diff --git a/raddb/mods-available/eap b/raddb/mods-available/eap index 8d007e9eb49f..3142dd83247e 100644 --- a/raddb/mods-available/eap +++ b/raddb/mods-available/eap @@ -163,26 +163,32 @@ eap { # you want. # tls-config tls-common { - private_key_password = whatever - private_key_file = ${certdir}/server.pem - - # - # If Private key & Certificate are located in the same file, - # then private_key_file & certificate_file must contain the - # same file name. - # - # If ca_file (below) is not used, then the certificate_file - # below MUST include not only the server certificate, but ALSO - # all of the CA certificates used to sign the server - # certificate. # - # Any certificate chain MUST be in order from server - # certificate (first in the file) to intermediaries (second) to - # root CA (last in the file) as per RFC 4346 (see - # certificate_list - # http://tools.ietf.org/html/rfc4346#section-7.4.2 ) + # Multiple certificate sections may be specified to support + # crypto agility with different key types. # - certificate_file = ${certdir}/server.pem + certificate { + private_key_password = whatever + private_key_file = ${certdir}/server.key + + # + # If Private key & Certificate are located in the same file, + # then private_key_file & certificate_file must contain the + # same file name. + # + # If ca_file (below) is not used, then the certificate_file + # below MUST include not only the server certificate, but ALSO + # all of the CA certificates used to sign the server + # certificate. + # + # Any certificate chain MUST be in order from server + # certificate (first in the file) to intermediaries (second) to + # root CA (last in the file) as per RFC 4346 (see + # certificate_list + # http://tools.ietf.org/html/rfc4346#section-7.4.2 ) + # + certificate_file = ${certdir}/server.pem + } # # Server certificate may also be specified at runtime on a per diff --git a/raddb/mods-available/eap_inner b/raddb/mods-available/eap_inner index d96cb4a7ff87..342863ab950e 100644 --- a/raddb/mods-available/eap_inner +++ b/raddb/mods-available/eap_inner @@ -64,9 +64,15 @@ eap inner-eap { # In the case of PEAP it also allows SoH with client certificates. # tls-config tls-peer { - private_key_password = whatever - private_key_file = ${certdir}/server.key - certificate_file = ${certdir}/server.pem + # + # Multiple certificate sections may be specified to support + # crypto agility with different key types. + # + certificate { + private_key_password = whatever + private_key_file = ${certdir}/server.key + certificate_file = ${certdir}/server.pem + } # # Trusted Root CA list diff --git a/raddb/sites-available/abfab-tls b/raddb/sites-available/abfab-tls index 79d74e6fcbb1..2ffd3b3db812 100644 --- a/raddb/sites-available/abfab-tls +++ b/raddb/sites-available/abfab-tls @@ -10,11 +10,14 @@ listen { proto = tcp tls { - private_key_password = whatever - # Moonshot tends to distribute certs separate from keys - private_key_file = ${certdir}/server.key - certificate_file = ${certdir}/server.pem + certificate { + private_key_file = ${certdir}/server.key + certificate_file = ${certdir}/server.pem + + private_key_password = whatever + } + ca_file = ${cadir}/ca.pem dh_file = ${certdir}/dh fragment_size = 8192 diff --git a/src/include/tls-h b/src/include/tls-h index f68381f2a90d..1ec92b46f951 100644 --- a/src/include/tls-h +++ b/src/include/tls-h @@ -213,6 +213,18 @@ typedef struct { } fr_tls_ocsp_conf_t; #endif +/** Structure representing a single certificate + * + */ +typedef struct { + bool pem_file_type; //!< Whether the file is expected to be PEM encoded. + ///< This allows us to load multiple chained PEM certificates + ///< from a single file. + char const *password; //!< Password to decrypt the certificate(s). + char const *certificate_file; //!< Path to certificate. + char const *private_key_file; //!< Path to certificate. +} fr_tls_conf_key_pair_t; + /* configured values goes right here */ struct fr_tls_conf_t { SSL_CTX **ctx; //!< We use an array of contexts to reduce contention. @@ -223,9 +235,8 @@ struct fr_tls_conf_t { CONF_SECTION *cs; - char const *private_key_password; //!< Password to decrypt the private key. - char const *private_key_file; //!< Private key file. - char const *certificate_file; //!< Public (certificate) file. + fr_tls_conf_key_pair_t **key_pairs; //!< One or more certificates + char const *random_file; //!< If set, we read 10K of data (or the complete file) //!< and use it to seed OpenSSL's PRNG. char const *ca_path; @@ -236,7 +247,6 @@ struct fr_tls_conf_t { uint32_t verify_depth; //!< Maximum number of certificates we can traverse //!< when attempting to reach the presented certificate //!< from our Root CA. - bool file_type; bool auto_chain; //!< Allow OpenSSL to build certificate chains //!< from all certificates it has available. //!< If false, the complete chain must be provided in diff --git a/src/lib/tls/conf.c b/src/lib/tls/conf.c index b122566817b0..e9d1f061b654 100644 --- a/src/lib/tls/conf.c +++ b/src/lib/tls/conf.c @@ -80,14 +80,24 @@ static CONF_PARSER ocsp_config[] = { }; #endif +CONF_PARSER tls_cert_pair_config[] = { + { FR_CONF_OFFSET("pem_file_type", FR_TYPE_BOOL, fr_tls_conf_key_pair_t, pem_file_type), .dflt = "yes" }, + { FR_CONF_OFFSET("certificate_file", FR_TYPE_FILE_INPUT | FR_TYPE_REQUIRED , fr_tls_conf_key_pair_t, certificate_file) }, + { FR_CONF_OFFSET("private_key_password", FR_TYPE_STRING | FR_TYPE_SECRET, fr_tls_conf_key_pair_t, password) }, + { FR_CONF_OFFSET("private_key_file", FR_TYPE_FILE_INPUT | FR_TYPE_REQUIRED, fr_tls_conf_key_pair_t, private_key_file) }, + + CONF_PARSER_TERMINATOR +}; + CONF_PARSER tls_server_config[] = { + { FR_CONF_DEPRECATED("pem_file_type", FR_TYPE_BOOL, fr_tls_conf_t, NULL) }, + { FR_CONF_DEPRECATED("certificate_file", FR_TYPE_FILE_INPUT, fr_tls_conf_t, NULL) }, + { FR_CONF_DEPRECATED("private_key_password", FR_TYPE_STRING | FR_TYPE_SECRET, fr_tls_conf_t, NULL) }, + { FR_CONF_DEPRECATED("private_key_file", FR_TYPE_FILE_INPUT, fr_tls_conf_t, NULL) }, + { FR_CONF_OFFSET("verify_depth", FR_TYPE_UINT32, fr_tls_conf_t, verify_depth), .dflt = "0" }, { FR_CONF_OFFSET("ca_path", FR_TYPE_FILE_INPUT, fr_tls_conf_t, ca_path) }, - { FR_CONF_OFFSET("pem_file_type", FR_TYPE_BOOL, fr_tls_conf_t, file_type), .dflt = "yes" }, - { FR_CONF_OFFSET("private_key_file", FR_TYPE_FILE_INPUT, fr_tls_conf_t, private_key_file) }, - { FR_CONF_OFFSET("certificate_file", FR_TYPE_FILE_INPUT, fr_tls_conf_t, certificate_file) }, { FR_CONF_OFFSET("ca_file", FR_TYPE_FILE_INPUT, fr_tls_conf_t, ca_file) }, - { FR_CONF_OFFSET("private_key_password", FR_TYPE_STRING | FR_TYPE_SECRET, fr_tls_conf_t, private_key_password) }, #ifdef PSK_MAX_IDENTITY_LEN { FR_CONF_OFFSET("psk_identity", FR_TYPE_STRING, fr_tls_conf_t, psk_identity) }, { FR_CONF_OFFSET("psk_hexphrase", FR_TYPE_STRING | FR_TYPE_SECRET, fr_tls_conf_t, psk_password) }, @@ -117,11 +127,14 @@ CONF_PARSER tls_server_config[] = { { FR_CONF_OFFSET("ecdh_curve", FR_TYPE_STRING, fr_tls_conf_t, ecdh_curve), .dflt = "prime256v1" }, #endif #endif - { FR_CONF_OFFSET("tls_max_version", FR_TYPE_FLOAT32, fr_tls_conf_t, tls_max_version) }, { FR_CONF_OFFSET("tls_min_version", FR_TYPE_FLOAT32, fr_tls_conf_t, tls_min_version), .dflt = "1.0" }, + { FR_CONF_OFFSET("certificate", FR_TYPE_SUBSECTION | FR_TYPE_MULTI, fr_tls_conf_t, key_pairs), + .subcs_size = sizeof(fr_tls_conf_key_pair_t), .subcs_type = "fr_tls_conf_key_pair_t", + .subcs = tls_cert_pair_config }, + { FR_CONF_POINTER("cache", FR_TYPE_SUBSECTION, NULL), .subcs = (void const *) cache_config }, { FR_CONF_POINTER("verify", FR_TYPE_SUBSECTION, NULL), .subcs = (void const *) verify_config }, @@ -135,13 +148,15 @@ CONF_PARSER tls_server_config[] = { }; CONF_PARSER tls_client_config[] = { + { FR_CONF_DEPRECATED("pem_file_type", FR_TYPE_BOOL, fr_tls_conf_t, NULL) }, + { FR_CONF_DEPRECATED("certificate_file", FR_TYPE_FILE_INPUT, fr_tls_conf_t, NULL) }, + { FR_CONF_DEPRECATED("private_key_password", FR_TYPE_STRING | FR_TYPE_SECRET, fr_tls_conf_t, NULL) }, + { FR_CONF_DEPRECATED("private_key_file", FR_TYPE_FILE_INPUT, fr_tls_conf_t, NULL) }, + { FR_CONF_OFFSET("verify_depth", FR_TYPE_UINT32, fr_tls_conf_t, verify_depth), .dflt = "0" }, { FR_CONF_OFFSET("ca_path", FR_TYPE_FILE_INPUT, fr_tls_conf_t, ca_path) }, - { FR_CONF_OFFSET("pem_file_type", FR_TYPE_BOOL, fr_tls_conf_t, file_type), .dflt = "yes" }, - { FR_CONF_OFFSET("private_key_file", FR_TYPE_FILE_INPUT, fr_tls_conf_t, private_key_file) }, - { FR_CONF_OFFSET("certificate_file", FR_TYPE_FILE_INPUT, fr_tls_conf_t, certificate_file) }, + { FR_CONF_OFFSET("ca_file", FR_TYPE_FILE_INPUT, fr_tls_conf_t, ca_file) }, - { FR_CONF_OFFSET("private_key_password", FR_TYPE_STRING | FR_TYPE_SECRET, fr_tls_conf_t, private_key_password) }, { FR_CONF_OFFSET("dh_file", FR_TYPE_STRING, fr_tls_conf_t, dh_file) }, { FR_CONF_OFFSET("random_file", FR_TYPE_STRING, fr_tls_conf_t, random_file) }, { FR_CONF_OFFSET("fragment_size", FR_TYPE_UINT32, fr_tls_conf_t, fragment_size), .dflt = "1024" }, @@ -160,46 +175,57 @@ CONF_PARSER tls_client_config[] = { { FR_CONF_OFFSET("tls_min_version", FR_TYPE_FLOAT32, fr_tls_conf_t, tls_min_version), .dflt = "1.0" }, + { FR_CONF_OFFSET("certificate", FR_TYPE_SUBSECTION | FR_TYPE_MULTI, fr_tls_conf_t, key_pairs), + .subcs_size = sizeof(fr_tls_conf_key_pair_t), .subcs_type = "fr_tls_conf_key_pair_t", + .subcs = tls_cert_pair_config }, + CONF_PARSER_TERMINATOR }; #ifdef __APPLE__ +/* + * We don't want to put the private key password in eap.conf, so check + * for our special string which indicates we should get the password + * programmatically. + */ +static char const *special_string = "Apple:UsecertAdmin"; + /** Use cert_admin to retrieve the password for the private key * */ static int conf_cert_admin_password(fr_tls_conf_t *conf) { - if (!conf->private_key_password) return 0; + size_t i, cnt; - /* - * We don't want to put the private key password in eap.conf, so check - * for our special string which indicates we should get the password - * programmatically. - */ - char const *special_string = "Apple:UsecertAdmin"; - if (strncmp(conf->private_key_password, special_string, strlen(special_string)) == 0) { - char cmd[256]; - char *password; - long const max_password_len = 128; - FILE *cmd_pipe; + if (!conf->key_pairs) return 0; + + cnt = talloc_array_length(conf->key_pairs); + for (i = 0; i < cnt; i++) { + char cmd[256]; + char *password; + long const max_password_len = 128; + FILE *cmd_pipe; + + if (!conf->key_pairs[i]->password) continue; + + if (strncmp(conf->key_pairs[i]->password, special_string, strlen(special_string)) != 0) continue; snprintf(cmd, sizeof(cmd) - 1, "/usr/sbin/certadmin --get-private-key-passphrase \"%s\"", - conf->private_key_file); + conf->key_pairs[i]->private_key_file); DEBUG2("Getting private key passphrase using command \"%s\"", cmd); cmd_pipe = popen(cmd, "r"); if (!cmd_pipe) { ERROR("%s command failed: Unable to get private_key_password", cmd); - ERROR("Error reading private_key_file %s", conf->private_key_file); + ERROR("Error reading private_key_file %s", conf->key_pairs[i]->private_key_file); return -1; } - talloc_const_free(conf->private_key_password); password = talloc_array(conf, char, max_password_len); if (!password) { ERROR("Can't allocate space for private_key_password"); - ERROR("Error reading private_key_file %s", conf->private_key_file); + ERROR("Error reading private_key_file %s", conf->key_pairs[i]->private_key_file); pclose(cmd_pipe); return -1; } @@ -211,7 +237,8 @@ static int conf_cert_admin_password(fr_tls_conf_t *conf) password[strlen(password) - 1] = '\0'; DEBUG3("Password from command = \"%s\"", password); - conf->private_key_password = password; + talloc_const_free(conf->key_pairs[i]->password); + conf->key_pairs[i]->password = password; } return 0; @@ -328,7 +355,7 @@ fr_tls_conf_t *tls_conf_parse_server(CONF_SECTION *cs) /* * Initialize TLS */ - conf->ctx = talloc_array(conf, SSL_CTX *, conf->ctx_count); + conf->ctx = talloc_zero_array(conf, SSL_CTX *, conf->ctx_count); for (i = 0; i < conf->ctx_count; i++) { conf->ctx[i] = tls_ctx_alloc(conf, false); if (conf->ctx[i] == NULL) goto error; diff --git a/src/lib/tls/ctx.c b/src/lib/tls/ctx.c index 85b9a6b058a1..639531540457 100644 --- a/src/lib/tls/ctx.c +++ b/src/lib/tls/ctx.c @@ -103,6 +103,73 @@ static int ctx_dh_params_load(SSL_CTX *ctx, char *file) return 0; } +static int tls_ctx_load_cert_key_pair(SSL_CTX *ctx, fr_tls_conf_key_pair_t const *key_pair) +{ + char *password; + int type; + + /* + * Conf parser should ensure they're both populated + */ + rad_assert(key_pair->certificate_file && key_pair->private_key_file); + + /* + * Identify the type of certificates that needs to be loaded + */ + if (key_pair->pem_file_type) { + type = SSL_FILETYPE_PEM; + } else { + type = SSL_FILETYPE_ASN1; + } + + /* + * Set the password (this should have been retrieved earlier) + */ + memcpy(&password, &key_pair->password, sizeof(password)); + SSL_CTX_set_default_passwd_cb_userdata(ctx, password); + + /* + * Always set the callback as it provides useful debug + * output if the certificate isn't set. + */ + SSL_CTX_set_default_passwd_cb(ctx, tls_session_password_cb); + + if (type == SSL_FILETYPE_PEM) { + if (!(SSL_CTX_use_certificate_chain_file(ctx, key_pair->certificate_file))) { + tls_log_error(NULL, "Failed reading certificate file \"%s\"", + key_pair->certificate_file); + return -1; + } + + } else { + if (!(SSL_CTX_use_certificate_file(ctx, key_pair->certificate_file, type))) { + tls_log_error(NULL, "Failed reading certificate file \"%s\"", + key_pair->certificate_file); + return -1; + } + } + + if (!(SSL_CTX_use_PrivateKey_file(ctx, key_pair->private_key_file, type))) { + tls_log_error(NULL, "Failed reading private key file \"%s\"", + key_pair->private_key_file); + return -1; + } + + /* + * Check if the last loaded private key matches one + * of the public certificates. + * + * Note: The call to SSL_CTX_use_certificate_chain_file + * can load in a private key too. + */ + if (!SSL_CTX_check_private_key(ctx)) { + ERROR("Private key does not match the certificate public key"); + return -1; + } + + return 0; +} + /** Create SSL context * * - Load the trusted CAs @@ -121,7 +188,6 @@ SSL_CTX *tls_ctx_alloc(fr_tls_conf_t const *conf, bool client) X509_STORE *cert_vpstore; int verify_mode = SSL_VERIFY_NONE; int ctx_options = 0; - int type; void *app_data_index; ctx = SSL_CTX_new(SSLv23_method()); /* which is really "all known SSL / TLS methods". Idiots. */ @@ -140,22 +206,6 @@ SSL_CTX *tls_ctx_alloc(fr_tls_conf_t const *conf, bool client) /* * Identify the type of certificates that needs to be loaded */ - if (conf->file_type) { - type = SSL_FILETYPE_PEM; - } else { - type = SSL_FILETYPE_ASN1; - } - - /* - * Set the private key password (this should have been retrieved earlier) - */ - { - char *password; - - memcpy(&password, &conf->private_key_password, sizeof(password)); - SSL_CTX_set_default_passwd_cb_userdata(ctx, password); - SSL_CTX_set_default_passwd_cb(ctx, tls_session_password_cb); - } #ifdef PSK_MAX_IDENTITY_LEN if (!client) { @@ -195,9 +245,7 @@ SSL_CTX *tls_ctx_alloc(fr_tls_conf_t const *conf, bool client) size_t psk_len, hex_len; uint8_t buffer[PSK_MAX_PSK_LEN]; - if (conf->certificate_file || - conf->private_key_password || conf->private_key_file || - conf->ca_file || conf->ca_path) { + if (conf->key_pairs || conf->ca_file || conf->ca_path) { ERROR("When PSKs are used, No certificate configuration is permitted"); return NULL; } @@ -237,44 +285,17 @@ SSL_CTX *tls_ctx_alloc(fr_tls_conf_t const *conf, bool client) * the cert chain needs to be given in PEM from * openSSL.org */ - if (!conf->certificate_file) goto load_ca; + if (conf->key_pairs) { + size_t i, cnt = talloc_array_length(conf->key_pairs); - if (type == SSL_FILETYPE_PEM) { - if (!(SSL_CTX_use_certificate_chain_file(ctx, conf->certificate_file))) { - tls_log_error(NULL, "Failed reading certificate file \"%s\"", - conf->certificate_file); - return NULL; - } - - } else { - if (!(SSL_CTX_use_certificate_file(ctx, conf->certificate_file, type))) { - tls_log_error(NULL, "Failed reading certificate file \"%s\"", - conf->certificate_file); - return NULL; - } - } - - if (conf->private_key_file) { - if (!(SSL_CTX_use_PrivateKey_file(ctx, conf->private_key_file, type))) { - tls_log_error(NULL, "Failed reading private key file \"%s\"", - conf->private_key_file); - return NULL; + for (i = 0; i < cnt; i++) { + if (tls_ctx_load_cert_key_pair(ctx, conf->key_pairs[i]) < 0) return NULL; } } /* - * Check if the loaded private key is the right one - * - * Note: The call to SSL_CTX_use_certificate_chain_file - * can load in a private key too. + * Load the CAs we trust */ - if (!SSL_CTX_check_private_key(ctx)) { - ERROR("Private key does not match the certificate public key"); - return NULL; - } - - /* Load the CAs we trust */ -load_ca: if (conf->ca_file || conf->ca_path) { if (!SSL_CTX_load_verify_locations(ctx, conf->ca_file, conf->ca_path)) { tls_log_error(NULL, "Failed reading Trusted root CA list \"%s\"", diff --git a/src/lib/tls/session.c b/src/lib/tls/session.c index 73d2204a4483..cd19f90a4b7e 100644 --- a/src/lib/tls/session.c +++ b/src/lib/tls/session.c @@ -1660,17 +1660,17 @@ tls_session_t *tls_session_init_server(TALLOC_CTX *ctx, fr_tls_conf_t *conf, REQ return NULL; } /* - * Better to perform explicit checks, that rely + * Better to perform explicit checks, than rely * on OpenSSL's opaque error messages. */ } else { - if (!conf->private_key_file) { + if (!conf->key_pairs || !conf->key_pairs[0]->private_key_file) { ERROR("TLS Server requires a private key file"); talloc_free(session); return NULL; } - if (!conf->certificate_file) { + if (!conf->key_pairs || !conf->key_pairs[0]->certificate_file) { ERROR("TLS Server requires a certificate file"); talloc_free(session); return NULL; diff --git a/src/tests/eapol_test/config/servers.conf b/src/tests/eapol_test/config/servers.conf index 9e7d6d18e03b..5cac0aa57647 100644 --- a/src/tests/eapol_test/config/servers.conf +++ b/src/tests/eapol_test/config/servers.conf @@ -55,9 +55,12 @@ modules { # eapol_test. # tls-config tls-common { - private_key_password = whatever - private_key_file = ${certdir}/server.pem - certificate_file = ${certdir}/server.pem + certificate { + private_key_password = whatever + private_key_file = ${certdir}/server.pem + certificate_file = ${certdir}/server.pem + } + ca_file = ${cadir}/ca.pem ca_path = ${cadir} dh_file = ${certdir}/dh