From e5bbda4df989848cbcd92263dd4b11a99b019b4f Mon Sep 17 00:00:00 2001 From: Nikos Mavrogiannopoulos Date: Sat, 15 Dec 2007 13:26:01 +0000 Subject: Initial support for openpgp keys --- NEWS | 5 ++ include/mod_gnutls.h.in | 14 +++ src/gnutls_config.c | 111 ++++++++++++++++++++++++ src/gnutls_hooks.c | 220 +++++++++++++++++++++++++++++++++++++++--------- src/mod_gnutls.c | 16 +++- 5 files changed, 326 insertions(+), 40 deletions(-) diff --git a/NEWS b/NEWS index 49abeda..b3a4cd9 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,7 @@ +** Version 0.5.0 + +- Added support for OpenPGP keys. + ** Version 0.4.2 (2007-12-10) - Added support for sending a certificate chain. @@ -23,3 +27,4 @@ to David Hrbáč. - Better handling of GnuTLSDHFile and GnuTLSRSAFile. - No longer default paths for RSA and DH parameter files. +[5~ \ No newline at end of file diff --git a/include/mod_gnutls.h.in b/include/mod_gnutls.h.in index a0f6581..db7e7dd 100644 --- a/include/mod_gnutls.h.in +++ b/include/mod_gnutls.h.in @@ -29,6 +29,8 @@ #include #include +#include +#include #include #ifndef __mod_gnutls_h_inc @@ -94,6 +96,8 @@ typedef struct gnutls_x509_crt_t certs_x509[MAX_CHAIN_SIZE]; /* A certificate chain */ unsigned int certs_x509_num; gnutls_x509_privkey_t privkey_x509; + gnutls_openpgp_crt_t cert_pgp; /* A certificate chain */ + gnutls_openpgp_privkey_t privkey_pgp; int enabled; /* whether to send the PEM encoded certificates * to CGIs @@ -108,6 +112,7 @@ typedef struct const char* srp_tpasswd_file; const char* srp_tpasswd_conf_file; gnutls_x509_crt_t ca_list[MAX_CA_CRTS]; + gnutls_openpgp_keyring_t pgp_list; unsigned int ca_list_size; int client_verify_mode; } mgs_srvconf_rec; @@ -254,6 +259,12 @@ const char *mgs_set_cert_file(cmd_parms * parms, void *dummy, const char *mgs_set_key_file(cmd_parms * parms, void *dummy, const char *arg); +const char *mgs_set_pgpcert_file(cmd_parms * parms, void *dummy, + const char *arg); + +const char *mgs_set_pgpkey_file(cmd_parms * parms, void *dummy, + const char *arg); + const char *mgs_set_cache(cmd_parms * parms, void *dummy, const char *type, const char* arg); @@ -266,6 +277,9 @@ const char *mgs_set_client_verify(cmd_parms * parms, void *dummy, const char *mgs_set_client_ca_file(cmd_parms * parms, void *dummy, const char *arg); +const char *mgs_set_keyring_file(cmd_parms * parms, void *dummy, + const char *arg); + const char *mgs_set_enabled(cmd_parms * parms, void *dummy, const char *arg); const char *mgs_set_export_certificates_enabled(cmd_parms * parms, void *dummy, diff --git a/src/gnutls_config.c b/src/gnutls_config.c index 8d6308a..4dccd08 100644 --- a/src/gnutls_config.c +++ b/src/gnutls_config.c @@ -202,6 +202,85 @@ const char *mgs_set_key_file(cmd_parms * parms, void *dummy, return NULL; } +const char *mgs_set_pgpcert_file(cmd_parms * parms, void *dummy, + const char *arg) +{ + int ret; + gnutls_datum_t data; + const char *file; + apr_pool_t *spool; + mgs_srvconf_rec *sc = + (mgs_srvconf_rec *) ap_get_module_config(parms->server-> + module_config, + &gnutls_module); + apr_pool_create(&spool, parms->pool); + + file = ap_server_root_relative(spool, arg); + + if (load_datum_from_file(spool, file, &data) != 0) { + return apr_psprintf(parms->pool, "GnuTLS: Error Reading " + "Certificate '%s'", file); + } + + ret = gnutls_openpgp_crt_init( &sc->cert_pgp); + if (ret < 0) { + return apr_psprintf(parms->pool, "GnuTLS: Failed to Init " + "PGP Certificate: (%d) %s", ret, + gnutls_strerror(ret)); + } + + + ret = + gnutls_openpgp_crt_import(sc->cert_pgp, &data, GNUTLS_OPENPGP_FMT_BASE64); + if (ret < 0) { + return apr_psprintf(parms->pool, "GnuTLS: Failed to Import " + "PGP Certificate '%s': (%d) %s", file, ret, + gnutls_strerror(ret)); + } + + apr_pool_destroy(spool); + return NULL; +} + +const char *mgs_set_pgpkey_file(cmd_parms * parms, void *dummy, + const char *arg) +{ + int ret; + gnutls_datum_t data; + const char *file; + apr_pool_t *spool; + mgs_srvconf_rec *sc = + (mgs_srvconf_rec *) ap_get_module_config(parms->server-> + module_config, + &gnutls_module); + apr_pool_create(&spool, parms->pool); + + file = ap_server_root_relative(spool, arg); + + if (load_datum_from_file(spool, file, &data) != 0) { + return apr_psprintf(parms->pool, "GnuTLS: Error Reading " + "Private Key '%s'", file); + } + + ret = gnutls_openpgp_privkey_init(&sc->privkey_pgp); + if (ret < 0) { + return apr_psprintf(parms->pool, "GnuTLS: Failed to initialize" + ": (%d) %s", ret, gnutls_strerror(ret)); + } + + ret = + gnutls_openpgp_privkey_import(sc->privkey_pgp, &data, + GNUTLS_OPENPGP_FMT_BASE64, NULL, 0); + if (ret != 0) { + return apr_psprintf(parms->pool, "GnuTLS: Failed to Import " + "PGP Private Key '%s': (%d) %s", file, ret, + gnutls_strerror(ret)); + } + apr_pool_destroy(spool); + return NULL; +} + + const char *mgs_set_srp_tpasswd_file(cmd_parms * parms, void *dummy, const char *arg) { @@ -350,6 +429,38 @@ const char *mgs_set_client_ca_file(cmd_parms * parms, void *dummy, return NULL; } +const char *mgs_set_keyring_file(cmd_parms * parms, void *dummy, + const char *arg) +{ + int rv; + const char *file; + apr_pool_t *spool; + gnutls_datum_t data; + + mgs_srvconf_rec *sc = + (mgs_srvconf_rec *) ap_get_module_config(parms->server-> + module_config, + &gnutls_module); + apr_pool_create(&spool, parms->pool); + + file = ap_server_root_relative(spool, arg); + + if (load_datum_from_file(spool, file, &data) != 0) { + return apr_psprintf(parms->pool, "GnuTLS: Error Reading " + "Keyring File '%s'", file); + } + + rv = gnutls_openpgp_keyring_import(sc->pgp_list, &data, GNUTLS_OPENPGP_FMT_BASE64); + if (rv < 0) { + return apr_psprintf(parms->pool, "GnuTLS: Failed to load " + "Keyring File '%s': (%d) %s", file, rv, + gnutls_strerror(rv)); + } + + apr_pool_destroy(spool); + return NULL; +} + const char *mgs_set_enabled(cmd_parms * parms, void *dummy, const char *arg) { diff --git a/src/gnutls_hooks.c b/src/gnutls_hooks.c index 55a1120..39fcd2b 100644 --- a/src/gnutls_hooks.c +++ b/src/gnutls_hooks.c @@ -36,7 +36,10 @@ static int mpm_is_threaded; static int mgs_cert_verify(request_rec * r, mgs_handle_t * ctxt); /* use side==0 for server and side==1 for client */ -static void mgs_add_common_cert_vars(request_rec * r, gnutls_x509_crt cert, +static void mgs_add_common_cert_vars(request_rec * r, gnutls_x509_crt_t cert, + int side, + int export_certificates_enabled); +static void mgs_add_common_pgpcert_vars(request_rec * r, gnutls_openpgp_crt_t cert, int side, int export_certificates_enabled); @@ -71,6 +74,10 @@ int ret; ret = gnutls_global_init(); if (ret < 0) /* FIXME: can we print here? */ exit(ret); + + ret = gnutls_global_init_extra(); + if (ret < 0) /* FIXME: can we print here? */ + exit(ret); apr_pool_cleanup_register(pconf, NULL, mgs_cleanup_pre_config, apr_pool_cleanup_null); @@ -87,14 +94,12 @@ int ret; return OK; } -/* We don't support openpgp certificates, yet */ -const static int cert_type_prio[2] = { GNUTLS_CRT_X509, 0 }; - static int mgs_select_virtual_server_cb(gnutls_session_t session) { mgs_handle_t *ctxt; mgs_srvconf_rec *tsc; int ret; + int cprio[3] = { GNUTLS_CRT_X509, GNUTLS_CRT_OPENPGP, 0 }; ctxt = gnutls_transport_get_ptr(session); @@ -126,8 +131,20 @@ static int mgs_select_virtual_server_cb(gnutls_session_t session) * negotiation. */ ret = gnutls_priority_set(session, ctxt->sc->priorities); - gnutls_certificate_type_set_priority(session, cert_type_prio); + /* Do not allow the user to override certificate priorities. We know + * better if the certificate of certain type is enabled. */ + if (ctxt->sc->cert_pgp != NULL && ctxt->sc->certs_x509[0] != NULL) { + gnutls_certificate_type_set_priority( session, cprio); + } else if (ctxt->sc->certs_x509[0] != NULL) { + cprio[0] = GNUTLS_CRT_X509; + cprio[1] = 0; + gnutls_certificate_type_set_priority( session, cprio); + } else if (ctxt->sc->cert_pgp != NULL) { + cprio[0] = GNUTLS_CRT_OPENPGP; + cprio[1] = 0; + gnutls_certificate_type_set_priority( session, cprio); + } /* actually it shouldn't fail since we have checked at startup */ if (ret < 0) @@ -143,13 +160,28 @@ static int cert_retrieve_fn(gnutls_session_t session, gnutls_retr_st * ret) ctxt = gnutls_transport_get_ptr(session); - ret->type = GNUTLS_CRT_X509; - ret->ncerts = ctxt->sc->certs_x509_num; - ret->deinit_all = 0; + if (gnutls_certificate_type_get( session) == GNUTLS_CRT_X509) { + ret->type = GNUTLS_CRT_X509; + ret->ncerts = ctxt->sc->certs_x509_num; + ret->deinit_all = 0; + + ret->cert.x509 = ctxt->sc->certs_x509; + ret->key.x509 = ctxt->sc->privkey_x509; + + return 0; + } else if (gnutls_certificate_type_get( session) == GNUTLS_CRT_OPENPGP) { + ret->type = GNUTLS_CRT_OPENPGP; + ret->ncerts = 1; + ret->deinit_all = 0; + + ret->cert.pgp = ctxt->sc->cert_pgp; + ret->key.pgp = ctxt->sc->privkey_pgp; + + return 0; + + } - ret->cert.x509 = ctxt->sc->certs_x509; - ret->key.x509 = ctxt->sc->privkey_x509; - return 0; + return GNUTLS_E_INTERNAL_ERROR; } const char static_dh_params[] = "-----BEGIN DH PARAMETERS-----\n" @@ -167,7 +199,7 @@ const char static_dh_params[] = "-----BEGIN DH PARAMETERS-----\n" * Returns negative on error. */ static int read_crt_cn(server_rec * s, apr_pool_t * p, - gnutls_x509_crt cert, char **cert_cn) + gnutls_x509_crt_t cert, char **cert_cn) { int rv = 0, i; size_t data_len; @@ -175,6 +207,7 @@ static int read_crt_cn(server_rec * s, apr_pool_t * p, *cert_cn = NULL; + data_len = 0; rv = gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, NULL, &data_len); @@ -186,7 +219,7 @@ static int read_crt_cn(server_rec * s, apr_pool_t * p, 0, *cert_cn, &data_len); } else { /* No CN return subject alternative name */ ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, - "No common name found in certificate for '%s:%d'. Looking for subject alternative name.", + "No common name found in certificate for '%s:%d'. Looking for subject alternative name...", s->server_hostname, s->port); rv = 0; /* read subject alternative name */ @@ -214,9 +247,33 @@ static int read_crt_cn(server_rec * s, apr_pool_t * p, } return rv; +} + +static int read_pgpcrt_cn(server_rec * s, apr_pool_t * p, + gnutls_openpgp_crt_t cert, char **cert_cn) +{ + int rv = 0; + size_t data_len; + + + *cert_cn = NULL; + + data_len = 0; + rv = gnutls_openpgp_crt_get_name(cert, 0, NULL, &data_len); + + if (rv == GNUTLS_E_SHORT_MEMORY_BUFFER && data_len > 1) { + *cert_cn = apr_palloc(p, data_len); + rv = gnutls_openpgp_crt_get_name(cert, 0, *cert_cn, &data_len); + } else { /* No CN return subject alternative name */ + ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, + "No name found in PGP certificate for '%s:%d'.", + s->server_hostname, s->port); + } + return rv; } + int mgs_hook_post_config(apr_pool_t * p, apr_pool_t * plog, apr_pool_t * ptemp, server_rec * base_server) @@ -350,6 +407,9 @@ mgs_hook_post_config(apr_pool_t * p, apr_pool_t * plog, if (sc->enabled == GNUTLS_ENABLED_TRUE) { rv = read_crt_cn(s, p, sc->certs_x509[0], &sc->cert_cn); + if (rv < 0) /* try openpgp certificate */ + rv = read_pgpcrt_cn(s, p, sc->cert_pgp, &sc->cert_cn); + if (rv < 0) { ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, "[GnuTLS] - Cannot find a certificate for host '%s:%d'!", @@ -478,15 +538,6 @@ mgs_srvconf_rec *mgs_find_sni_server(gnutls_session_t session) ctxt = gnutls_transport_get_ptr(session); - sni_type = gnutls_certificate_type_get(session); - if (sni_type != GNUTLS_CRT_X509) { - /* In theory, we could support OpenPGP Certificates. Theory != code. */ - ap_log_error(APLOG_MARK, APLOG_CRIT, 0, - ctxt->c->base_server, - "GnuTLS: Only x509 Certificates are currently supported."); - return NULL; - } - rv = gnutls_server_name_get(ctxt->session, sni_name, &data_len, &sni_type, 0); @@ -684,7 +735,11 @@ int mgs_hook_fixups(request_rec * r) tmp = mgs_session_id2sz(sbuf, len, buf, sizeof(buf)); apr_table_setn(env, "SSL_SESSION_ID", apr_pstrdup(r->pool, tmp)); - mgs_add_common_cert_vars(r, ctxt->sc->certs_x509[0], 0, + if (gnutls_certificate_type_get( ctxt->session) == GNUTLS_CRT_X509) + mgs_add_common_cert_vars(r, ctxt->sc->certs_x509[0], 0, + ctxt->sc->export_certificates_enabled); + else if (gnutls_certificate_type_get( ctxt->session) == GNUTLS_CRT_OPENPGP) + mgs_add_common_pgpcert_vars(r, ctxt->sc->cert_pgp, 0, ctxt->sc->export_certificates_enabled); return rv; @@ -746,7 +801,7 @@ int mgs_hook_authz(request_rec * r) */ #define MGS_SIDE ((side==0)?"SSL_SERVER":"SSL_CLIENT") static void -mgs_add_common_cert_vars(request_rec * r, gnutls_x509_crt cert, int side, +mgs_add_common_cert_vars(request_rec * r, gnutls_x509_crt_t cert, int side, int export_certificates_enabled) { unsigned char sbuf[64]; /* buffer to hold serials */ @@ -852,17 +907,82 @@ mgs_add_common_cert_vars(request_rec * r, gnutls_x509_crt cert, int side, } } } +} +static void +mgs_add_common_pgpcert_vars(request_rec * r, gnutls_openpgp_crt_t cert, int side, + int export_certificates_enabled) +{ + unsigned char sbuf[64]; /* buffer to hold serials */ + char buf[AP_IOBUFSIZE]; + const char *tmp; + size_t len; + int ret; -} + apr_table_t *env = r->subprocess_env; + + if (export_certificates_enabled != 0) { + char cert_buf[10 * 1024]; + len = sizeof(cert_buf); + + if (gnutls_openpgp_crt_export + (cert, GNUTLS_OPENPGP_FMT_BASE64, cert_buf, &len) >= 0) + apr_table_setn(env, + apr_pstrcat(r->pool, MGS_SIDE, "_CERT", NULL), + apr_pstrmemdup(r->pool, cert_buf, len)); + + } + + len = sizeof(buf); + gnutls_openpgp_crt_get_name(cert, 0, buf, &len); + apr_table_setn(env, apr_pstrcat(r->pool, MGS_SIDE, "_NAME", NULL), + apr_pstrmemdup(r->pool, buf, len)); + + len = sizeof(sbuf); + gnutls_openpgp_crt_get_fingerprint(cert, sbuf, &len); + tmp = mgs_session_id2sz(sbuf, len, buf, sizeof(buf)); + apr_table_setn(env, apr_pstrcat(r->pool, MGS_SIDE, "_FINGERPRINT", NULL), + apr_pstrdup(r->pool, tmp)); + + ret = gnutls_openpgp_crt_get_version(cert); + if (ret > 0) + apr_table_setn(env, + apr_pstrcat(r->pool, MGS_SIDE, "_M_VERSION", NULL), + apr_psprintf(r->pool, "%u", ret)); + + apr_table_setn(env, + apr_pstrcat(r->pool, MGS_SIDE, "_CERT_TYPE", NULL), "OPENPGP"); + + tmp = + mgs_time2sz(gnutls_openpgp_crt_get_expiration_time + (cert), buf, sizeof(buf)); + apr_table_setn(env, apr_pstrcat(r->pool, MGS_SIDE, "_V_END", NULL), + apr_pstrdup(r->pool, tmp)); + tmp = + mgs_time2sz(gnutls_openpgp_crt_get_creation_time + (cert), buf, sizeof(buf)); + apr_table_setn(env, apr_pstrcat(r->pool, MGS_SIDE, "_V_START", NULL), + apr_pstrdup(r->pool, tmp)); + ret = gnutls_openpgp_crt_get_pk_algorithm(cert, NULL); + if (ret >= 0) { + apr_table_setn(env, apr_pstrcat(r->pool, MGS_SIDE, "_A_KEY", NULL), + gnutls_pk_algorithm_get_name(ret)); + } + +} + +/* FIXME: Allow client sending a certificate chain */ static int mgs_cert_verify(request_rec * r, mgs_handle_t * ctxt) { const gnutls_datum_t *cert_list; unsigned int cert_list_size, status, expired; int rv, ret; - gnutls_x509_crt_t cert; + union { + gnutls_x509_crt_t x509; + gnutls_openpgp_crt_t pgp; + } cert; apr_time_t activation_time, expiration_time, cur_time; cert_list = @@ -888,23 +1008,40 @@ static int mgs_cert_verify(request_rec * r, mgs_handle_t * ctxt) return HTTP_FORBIDDEN; } - gnutls_x509_crt_init(&cert); - rv = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER); + if (gnutls_certificate_type_get( ctxt->session) == GNUTLS_CRT_X509) { + gnutls_x509_crt_init(&cert.x509); + rv = gnutls_x509_crt_import(cert.x509, &cert_list[0], GNUTLS_X509_FMT_DER); + } else if (gnutls_certificate_type_get( ctxt->session) == GNUTLS_CRT_OPENPGP) { + gnutls_openpgp_crt_init(&cert.pgp); + rv = gnutls_openpgp_crt_import(cert.pgp, &cert_list[0], GNUTLS_OPENPGP_FMT_RAW); + } else return HTTP_FORBIDDEN; + if (rv < 0) { - ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "GnuTLS: Failed to Verify Peer: " "Failed to import peer certificates."); - ret = HTTP_FORBIDDEN; - goto exit; + ret = HTTP_FORBIDDEN; + goto exit; } - apr_time_ansi_put(&expiration_time, - gnutls_x509_crt_get_expiration_time(cert)); - apr_time_ansi_put(&activation_time, - gnutls_x509_crt_get_activation_time(cert)); + if (gnutls_certificate_type_get( ctxt->session) == GNUTLS_CRT_X509) { + apr_time_ansi_put(&expiration_time, + gnutls_x509_crt_get_expiration_time(cert.x509)); + apr_time_ansi_put(&activation_time, + gnutls_x509_crt_get_activation_time(cert.x509)); - rv = gnutls_x509_crt_verify(cert, ctxt->sc->ca_list, + rv = gnutls_x509_crt_verify(cert.x509, ctxt->sc->ca_list, ctxt->sc->ca_list_size, 0, &status); + } else { + apr_time_ansi_put(&expiration_time, + gnutls_openpgp_crt_get_expiration_time(cert.pgp)); + apr_time_ansi_put(&activation_time, + gnutls_openpgp_crt_get_creation_time(cert.pgp)); + + rv = gnutls_openpgp_crt_verify_ring(cert.pgp, ctxt->sc->pgp_list, + 0, &status); + } + if (rv < 0) { ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, @@ -957,7 +1094,11 @@ static int mgs_cert_verify(request_rec * r, mgs_handle_t * ctxt) // mgs_hook_fixups(r); // rv = mgs_authz_lua(r); - mgs_add_common_cert_vars(r, cert, 1, + if (gnutls_certificate_type_get( ctxt->session) == GNUTLS_CRT_X509) + mgs_add_common_cert_vars(r, cert.x509, 1, + ctxt->sc->export_certificates_enabled); + else if (gnutls_certificate_type_get( ctxt->session) == GNUTLS_CRT_OPENPGP) + mgs_add_common_pgpcert_vars(r, cert.pgp, 1, ctxt->sc->export_certificates_enabled); { @@ -981,7 +1122,10 @@ static int mgs_cert_verify(request_rec * r, mgs_handle_t * ctxt) } exit: - gnutls_x509_crt_deinit(cert); + if (gnutls_certificate_type_get( ctxt->session) == GNUTLS_CRT_X509) + gnutls_x509_crt_deinit(cert.x509); + else if (gnutls_certificate_type_get( ctxt->session) == GNUTLS_CRT_OPENPGP) + gnutls_openpgp_crt_deinit(cert.pgp); return ret; diff --git a/src/mod_gnutls.c b/src/mod_gnutls.c index a6e5528..4d70fbd 100644 --- a/src/mod_gnutls.c +++ b/src/mod_gnutls.c @@ -64,6 +64,10 @@ static const command_rec mgs_config_cmds[] = { NULL, RSRC_CONF, "Set the CA File to verify Client Certificates"), + AP_INIT_TAKE1("GnuTLSPGPKeyringFile", mgs_set_keyring_file, + NULL, + RSRC_CONF, + "Set the Keyring File to verify Client Certificates"), AP_INIT_TAKE1("GnuTLSDHFile", mgs_set_dh_file, NULL, RSRC_CONF, @@ -75,11 +79,19 @@ static const command_rec mgs_config_cmds[] = { AP_INIT_TAKE1("GnuTLSCertificateFile", mgs_set_cert_file, NULL, RSRC_CONF, - "SSL Server Key file"), + "SSL Server Certificate file"), AP_INIT_TAKE1("GnuTLSKeyFile", mgs_set_key_file, NULL, RSRC_CONF, - "SSL Server SRP Password file"), + "SSL Server Private Key file"), + AP_INIT_TAKE1("GnuTLSPGPCertificateFile", mgs_set_pgpcert_file, + NULL, + RSRC_CONF, + "SSL Server PGP Certificate file"), + AP_INIT_TAKE1("GnuTLSPGPKeyFile", mgs_set_pgpkey_file, + NULL, + RSRC_CONF, + "SSL Server PGP Private key file"), AP_INIT_TAKE1("GnuTLSSRPPasswdFile", mgs_set_srp_tpasswd_file, NULL, RSRC_CONF, -- cgit