UTF-8 X.509 distinguished names

The UTF-8 support that came with commit 2627335 does allow international
usernames and passwords. This patch introduces UTF-8 support for X.509 DNs.
Additionally, instead of using the legacy openssl format, DNs are now
displayed in RFC 2253 format; "/C=ru/L=\xD0\x9C\xD0\xBE\xD1\x81\xD0\xBA\xD0
\xB2\xD0\xB0/O=\xD0\x9A\xD1\x80\xD0\xB5\xD0\xBC\xD0\xBB\xD1\x8C/CN=kreml.ru"
becomes "C=ru, L=Москва, O=Кремль, CN=kreml.ru".

Since the specific character classes for X.509 names are removed, the
"no-name-remapping" configuration option has no use anymore and is removed
as well.

Signed-off-by: Heiko Hund <heiko.hund@sophos.com>
Acked-by: Adriaan de Jong <dejong@fox-it.com>
Acked-by: David Sommerseth <davids@redhat.com>
Signed-off-by: David Sommerseth <davids@redhat.com>
This commit is contained in:
Heiko Hund 2012-02-04 12:56:24 +00:00 committed by David Sommerseth
parent fc3ee19dee
commit 5e86fd9377
7 changed files with 55 additions and 75 deletions

View file

@ -3322,27 +3322,6 @@ the authenticated username as the common name,
rather than the common name from the client cert.
.\"*********************************************************
.TP
.B \-\-no-name-remapping
Allow Common Name, X509 Subject, and username strings to include
any printable character including space, but excluding control
characters such as tab, newline, and carriage-return.
By default, OpenVPN will remap
any character other than alphanumeric, underbar ('_'), dash
('-'), dot ('.'), and slash ('/') to underbar ('_'). The X509
Subject string as returned by the
.B tls_id
environmental variable, can additionally contain colon (':') or
equal ('=').
While name remapping is performed for security reasons to reduce
the possibility of introducing string expansion security vulnerabilities
in user-defined authentication
scripts, this option is provided for those cases where it is desirable to
disable the remapping feature. Don't use this option unless you
know what you are doing!
.\"*********************************************************
.TP
.B \-\-port-share host port [dir]
When run in TCP server mode, share the OpenVPN port with
another application, such as an HTTPS server. If OpenVPN
@ -4463,7 +4442,7 @@ When
.B cmd
is executed two arguments are appended, as follows:
.B cmd certificate_depth X509_NAME_oneline
.B cmd certificate_depth subject
These arguments are, respectively, the current certificate depth and
the X509 common name (cn) of the peer.

View file

@ -601,7 +601,7 @@ static const char usage_message[] =
" pending TLS connection that has otherwise passed all other\n"
" tests of certification. cmd should return 0 to allow\n"
" TLS handshake to proceed, or 1 to fail. (cmd is\n"
" executed as 'cmd certificate_depth X509_NAME_oneline')\n"
" executed as 'cmd certificate_depth subject')\n"
"--tls-export-cert [directory] : Get peer cert in PEM format and store it \n"
" in an openvpn temporary file in [directory]. Peer cert is \n"
" stored before tls-verify script execution and deleted after.\n"
@ -2164,9 +2164,6 @@ options_postprocess_verify_ce (const struct options *options, const struct conne
if ((options->ssl_flags & SSLF_AUTH_USER_PASS_OPTIONAL) && !ccnr)
msg (M_USAGE, "--auth-user-pass-optional %s", postfix);
}
if ((options->ssl_flags & SSLF_NO_NAME_REMAPPING) && script_method == SM_SYSTEM)
msg (M_USAGE, "--script-security method='system' cannot be combined with --no-name-remapping");
}
else
{
@ -2201,8 +2198,6 @@ options_postprocess_verify_ce (const struct options *options, const struct conne
msg (M_USAGE, "--username-as-common-name requires --mode server");
if (options->ssl_flags & SSLF_AUTH_USER_PASS_OPTIONAL)
msg (M_USAGE, "--auth-user-pass-optional requires --mode server");
if (options->ssl_flags & SSLF_NO_NAME_REMAPPING)
msg (M_USAGE, "--no-name-remapping requires --mode server");
if (options->ssl_flags & SSLF_OPT_VERIFY)
msg (M_USAGE, "--opt-verify requires --mode server");
if (options->server_flags & SF_TCP_NODELAY_HELPER)
@ -5581,11 +5576,6 @@ add_option (struct options *options,
VERIFY_PERMISSION (OPT_P_GENERAL);
options->ssl_flags |= SSLF_AUTH_USER_PASS_OPTIONAL;
}
else if (streq (p[0], "no-name-remapping"))
{
VERIFY_PERMISSION (OPT_P_GENERAL);
options->ssl_flags |= SSLF_NO_NAME_REMAPPING;
}
else if (streq (p[0], "opt-verify"))
{
VERIFY_PERMISSION (OPT_P_GENERAL);

View file

@ -129,7 +129,7 @@ pkcs11_certificate_dn (pkcs11h_certificate_t certificate, char *dn,
goto cleanup;
}
X509_NAME_oneline (X509_get_subject_name (x509), dn, dn_len);
_openssl_get_subject (x509, dn, dn_len);
ret = 0;

View file

@ -3,7 +3,7 @@
# verify-cn -- a sample OpenVPN tls-verify script
#
# Return 0 if cn matches the common name component of
# X509_NAME_oneline, 1 otherwise.
# subject, 1 otherwise.
#
# For example in OpenVPN, you could use the directive:
#
@ -13,7 +13,7 @@
# the client common name is listed on a line in the
# allowed_clients file.
die "usage: verify-cn cnfile certificate_depth X509_NAME_oneline" if (@ARGV != 3);
die "usage: verify-cn cnfile certificate_depth subject" if (@ARGV != 3);
# Parse out arguments:
# cnfile -- The file containing the list of common names, one per
@ -37,7 +37,7 @@ if ($depth == 0) {
# If so, parse out the common name substring in
# the X509 subject string.
if ($x509 =~ /\/CN=([^\/]+)/) {
if ($x509 =~ / CN=([^,]+)/) {
$cn = $1;
# Accept the connection if the X509 common name
# string matches the passed cn argument.

View file

@ -40,24 +40,9 @@
#include "ssl_verify_openssl.h"
#endif
/** Legal characters in an X509 name */
#define X509_NAME_CHAR_CLASS (CC_ALNUM|CC_UNDERBAR|CC_DASH|CC_DOT|CC_AT|CC_COLON|CC_SLASH|CC_EQUAL)
/** Legal characters in a common name */
#define COMMON_NAME_CHAR_CLASS (CC_ALNUM|CC_UNDERBAR|CC_DASH|CC_DOT|CC_AT|CC_SLASH)
/** Maximum length of common name */
#define TLS_USERNAME_LEN 64
static void
string_mod_sslname (char *str, const unsigned int restrictive_flags, const unsigned int ssl_flags)
{
if (ssl_flags & SSLF_NO_NAME_REMAPPING)
string_mod (str, CC_PRINT, CC_CRLF, '_');
else
string_mod (str, restrictive_flags, 0, '_');
}
/*
* Export the untrusted IP address and port to the environment
*/
@ -595,7 +580,7 @@ verify_cert(struct tls_session *session, x509_cert_t *cert, int cert_depth)
}
/* enforce character class restrictions in X509 name */
string_mod_sslname (subject, X509_NAME_CHAR_CLASS, opt->ssl_flags);
string_mod (subject, CC_PRINT, CC_CRLF, '_');
string_replace_leading (subject, '-', '_');
/* extract the username (default is CN) */
@ -615,7 +600,7 @@ verify_cert(struct tls_session *session, x509_cert_t *cert, int cert_depth)
}
/* enforce character class restrictions in common name */
string_mod_sslname (common_name, COMMON_NAME_CHAR_CLASS, opt->ssl_flags);
string_mod (common_name, CC_PRINT, CC_CRLF, '_');
/* warn if cert chain is too deep */
if (cert_depth >= MAX_CERT_DEPTH)
@ -1005,7 +990,7 @@ verify_user_pass_script (struct tls_session *session, const struct user_pass *up
* Verify the username and password using a plugin
*/
static int
verify_user_pass_plugin (struct tls_session *session, const struct user_pass *up, const char *raw_username)
verify_user_pass_plugin (struct tls_session *session, const struct user_pass *up)
{
int retval = OPENVPN_PLUGIN_FUNC_ERROR;
struct key_state *ks = &session->key[KS_PRIMARY]; /* primary key */
@ -1014,7 +999,7 @@ verify_user_pass_plugin (struct tls_session *session, const struct user_pass *up
if ((session->opt->ssl_flags & SSLF_AUTH_USER_PASS_OPTIONAL) || strlen (up->username))
{
/* set username/password in private env space */
setenv_str (session->opt->es, "username", raw_username);
setenv_str (session->opt->es, "username", up->username);
setenv_str (session->opt->es, "password", up->password);
/* setenv incoming cert common name for script */
@ -1038,7 +1023,6 @@ verify_user_pass_plugin (struct tls_session *session, const struct user_pass *up
#endif
setenv_del (session->opt->es, "password");
setenv_str (session->opt->es, "username", up->username);
}
else
{
@ -1059,7 +1043,7 @@ verify_user_pass_plugin (struct tls_session *session, const struct user_pass *up
#define KMDA_DEF 3
static int
verify_user_pass_management (struct tls_session *session, const struct user_pass *up, const char *raw_username)
verify_user_pass_management (struct tls_session *session, const struct user_pass *up)
{
int retval = KMDA_ERROR;
struct key_state *ks = &session->key[KS_PRIMARY]; /* primary key */
@ -1068,7 +1052,7 @@ verify_user_pass_management (struct tls_session *session, const struct user_pass
if ((session->opt->ssl_flags & SSLF_AUTH_USER_PASS_OPTIONAL) || strlen (up->username))
{
/* set username/password in private env space */
setenv_str (session->opt->es, "username", raw_username);
setenv_str (session->opt->es, "username", up->username);
setenv_str (session->opt->es, "password", up->password);
/* setenv incoming cert common name for script */
@ -1081,7 +1065,6 @@ verify_user_pass_management (struct tls_session *session, const struct user_pass
management_notify_client_needing_auth (management, ks->mda_key_id, session->opt->mda_context, session->opt->es);
setenv_del (session->opt->es, "password");
setenv_str (session->opt->es, "username", up->username);
retval = KMDA_SUCCESS;
}
@ -1105,9 +1088,6 @@ verify_user_pass(struct user_pass *up, struct tls_multi *multi,
bool s2 = true;
struct key_state *ks = &session->key[KS_PRIMARY]; /* primary key */
struct gc_arena gc = gc_new ();
char *raw_username;
#ifdef MANAGEMENT_DEF_AUTH
int man_def_auth = KMDA_UNDEF;
@ -1115,22 +1095,17 @@ verify_user_pass(struct user_pass *up, struct tls_multi *multi,
man_def_auth = KMDA_DEF;
#endif
/* preserve raw username before string_mod remapping, for plugins */
ALLOC_ARRAY_CLEAR_GC (raw_username, char, USER_PASS_LEN, &gc);
strcpy (raw_username, up->username);
string_mod (raw_username, CC_PRINT, CC_CRLF, '_');
/* enforce character class restrictions in username/password */
string_mod_sslname (up->username, COMMON_NAME_CHAR_CLASS, session->opt->ssl_flags);
string_mod (up->username, CC_PRINT, CC_CRLF, '_');
string_mod (up->password, CC_PRINT, CC_CRLF, '_');
/* call plugin(s) and/or script */
#ifdef MANAGEMENT_DEF_AUTH
if (man_def_auth == KMDA_DEF)
man_def_auth = verify_user_pass_management (session, up, raw_username);
man_def_auth = verify_user_pass_management (session, up);
#endif
if (plugin_defined (session->opt->plugins, OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY))
s1 = verify_user_pass_plugin (session, up, raw_username);
s1 = verify_user_pass_plugin (session, up);
if (session->opt->auth_user_pass_verify_script)
s2 = verify_user_pass_script (session, up);
@ -1179,8 +1154,6 @@ verify_user_pass(struct user_pass *up, struct tls_multi *multi,
{
msg (D_TLS_ERRORS, "TLS Auth Error: Auth Username/Password verification failed for peer");
}
gc_free (&gc);
}
void

View file

@ -246,17 +246,53 @@ x509_free_sha1_hash (unsigned char *hash)
free(hash);
}
char *
_openssl_get_subject (X509 *cert, char *buf, int size)
{
BIO *subject_bio;
BUF_MEM *subject_mem;
char *subject = buf;
int maxlen = size;
subject_bio = BIO_new (BIO_s_mem ());
if (subject_bio == NULL)
goto out;
X509_NAME_print_ex (subject_bio, X509_get_subject_name (cert),
0, XN_FLAG_SEP_CPLUS_SPC | XN_FLAG_FN_SN |
ASN1_STRFLGS_UTF8_CONVERT | ASN1_STRFLGS_ESC_CTRL);
if (BIO_eof (subject_bio))
goto out_free;
BIO_get_mem_ptr (subject_bio, &subject_mem);
if (subject == NULL)
{
maxlen = subject_mem->length + 1;
subject = malloc (maxlen);
check_malloc_return (subject);
}
memcpy (subject, subject_mem->data, maxlen);
subject[maxlen - 1] = '\0';
out_free:
BIO_free (subject_bio);
out:
return subject;
}
char *
x509_get_subject (X509 *cert)
{
return X509_NAME_oneline (X509_get_subject_name (cert), NULL, 0);
return _openssl_get_subject (cert, NULL, 0);
}
void
x509_free_subject (char *subject)
{
if (subject)
OPENSSL_free(subject);
free(subject);
}

View file

@ -69,4 +69,6 @@ int verify_callback (int preverify_ok, X509_STORE_CTX * ctx);
/** @} name Function for authenticating a new connection from a remote OpenVPN peer */
char *_openssl_get_subject (X509 *cert, char *buf, int size);
#endif /* SSL_VERIFY_OPENSSL_H_ */