From 11d70bb0541c4bdd7aafb9d8b4d25e3ea07f2b89 Mon Sep 17 00:00:00 2001 From: Nalin Dahyabhai Date: Tue, 28 Sep 2010 18:03:36 -0400 Subject: [PATCH 017/150] - give cert_select a body - fill in bits that parse and store subject-alt-name values --- src/plugins/preauth/pkinit/pkinit_crypto_nss.c | 377 +++++++++++++++++++++++- 1 files changed, 373 insertions(+), 4 deletions(-) diff --git a/src/plugins/preauth/pkinit/pkinit_crypto_nss.c b/src/plugins/preauth/pkinit/pkinit_crypto_nss.c index 38b3fba..d4c4411 100644 --- a/src/plugins/preauth/pkinit/pkinit_crypto_nss.c +++ b/src/plugins/preauth/pkinit/pkinit_crypto_nss.c @@ -49,6 +49,13 @@ #define CONFIGDIR "/etc/pki/nssdb" +/* Forward declarations. */ +static krb5_error_code cert_retrieve_cert_sans(krb5_context context, + CERTCertificate *cert, + krb5_principal **pkinit_sans, + krb5_principal **upn_sans, + unsigned char ***kdc_hostname); + /* Plugin and request state. */ struct _pkinit_plg_crypto_context { PLArenaPool *pool; @@ -1123,6 +1130,19 @@ static SECItem pkinit_kp_mssclogin = { .data = oid_ms_sc_login_key_purpose_bytes, .len = 10, }; +static unsigned char oid_pkinit_name_type_principal_bytes[] = + {0x2b, 0x06, 0x01, 0x05, 0x02, 0x02}; +static SECItem pkinit_nt_principal = { + .data = oid_pkinit_name_type_principal_bytes, + .len = 6, +}; +static unsigned char oid_pkinit_name_type_upn_bytes[] = + {0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x14, 0x02, 0x03}; +static SECItem pkinit_nt_upn = { + .data = oid_pkinit_name_type_upn_bytes, + .len = 10, +}; + static unsigned int cert_get_eku_bits(krb5_context context, CERTCertificate *cert, int kdc) { @@ -1194,7 +1214,13 @@ crypto_cert_get_matching_data(krb5_context context, /* FIXME: not RFC2253 */ md->ku_bits = cert_get_ku_bits(context, cert_handle->cert); md->eku_bits = cert_get_eku_bits(context, cert_handle->cert, 0); - md->sans = NULL; /* FIXME */ + if (cert_retrieve_cert_sans(context, cert->cert, + &md->sans, &md->sans, NULL) != 0) { + free(md->subject_dn); + free(md->issuer_dn); + free(md); + return ENOMEM; + } *ret_data = md; return 0; } @@ -1221,9 +1247,14 @@ crypto_cert_free_matching_data(krb5_context context, /* Mark the cert tracked in the matching data structure as the one we're going * to use. */ krb5_error_code -crypto_cert_select(krb5_context context, - pkinit_cert_matching_data *data) +crypto_cert_select(krb5_context context, pkinit_cert_matching_data *data) { + CERTCertificate *cert; + cert = CERT_DupCertificate(data->ch->cert); + if (data->ch->id_cryptoctx->cert != NULL) { + CERT_DestroyCertificate(data->ch->id_cryptoctx->cert); + } + data->ch->id_cryptoctx->cert = cert; return ENOSYS; } @@ -1347,6 +1378,340 @@ pkinit_octetstring2key(krb5_context context, return ENOSYS; } +static int +cert_add_string(unsigned char ***list, int *count, + int len, const unsigned char *value) +{ + unsigned char **tmp; + tmp = malloc(sizeof(tmp[0]) * (*count + 2)); + if (tmp == NULL) { + return ENOMEM; + } + memcpy(tmp, *list, *count * sizeof(tmp[0])); + tmp[*count] = malloc(len + 1); + if (tmp[*count] == NULL) { + free(tmp); + return ENOMEM; + } + memcpy(tmp[*count], value, len); + tmp[*count][len] = '\0'; + tmp[*count + 1] = NULL; + if (*count != 0) { + free(*list); + } + *list = tmp; + (*count)++; + return 0; +} + +static int +cert_add_princ(krb5_context context, krb5_principal **sans, int *n_sans, + krb5_principal princ) +{ + krb5_principal *tmp; + tmp = malloc(sizeof(krb5_principal *) * (*n_sans + 2)); + if (tmp == NULL) { + return ENOMEM; + } + memcpy(tmp, *sans, sizeof(tmp[0]) * *n_sans); + if (krb5_copy_principal(context, princ, &tmp[*n_sans]) != 0) { + free(tmp); + return ENOMEM; + } + tmp[*n_sans + 1] = NULL; + if (*n_sans > 0) { + free(*sans); + } + *sans = tmp; + (*n_sans)++; + return 0; +} + +static const SEC_ASN1Template +nt_upn_template[] = { + { + .kind = SEC_ASN1_CONTEXT_SPECIFIC | 0 | + SEC_ASN1_EXPLICIT | + SEC_ASN1_CONSTRUCTED, + .offset = 0, + .sub = SEC_UTF8StringTemplate, + .size = sizeof(SECItem), + }, +}; +static int +cert_add_upn(PLArenaPool *pool, krb5_context context, + krb5_principal **sans, int *n_sans, SECItem *name) +{ + SECItem decoded; + char *unparsed; + krb5_principal tmp; + int i; + + /* Decode the string. */ + if (SEC_ASN1DecodeItem(pool, &decoded, nt_upn_template, + name) != SECSuccess) { + return ENOMEM; + } + unparsed = malloc(decoded.len + 1); + if (unparsed == NULL) { + return ENOMEM; + } + memcpy(unparsed, decoded.data, decoded.len); + unparsed[decoded.len] = '\0'; + /* Parse the string into a principal name. */ + if (krb5_parse_name(context, unparsed, &tmp) != 0) { + free(unparsed); + return ENOMEM; + } + free(unparsed); + /* Unparse the name back into a string and make sure it matches what + * was in the certificate. */ + if (krb5_unparse_name(context, tmp, &unparsed) != 0) { + krb5_free_principal(context, tmp); + return ENOMEM; + } + if ((strlen(unparsed) != decoded.len) || + (memcmp(unparsed, decoded.data, decoded.len) != 0)) { + krb5_free_unparsed_name(context, unparsed); + krb5_free_principal(context, tmp); + return ENOMEM; + } + /* Add the principal name to the list. */ + i = cert_add_princ(context, sans, n_sans, tmp); + krb5_free_unparsed_name(context, unparsed); + krb5_free_principal(context, tmp); + return i; +} + +struct realm { + SECItem name; +}; +struct principal_name { + SECItem name_type; + SECItem **name_string; +}; +struct kerberos_principal_name { + SECItem realm; + struct principal_name principal_name; +}; +static const SEC_ASN1Template +kerberos_string_template[] = { + { + .kind = SEC_ASN1_GENERAL_STRING, + .offset = 0, + .sub = NULL, + .size = sizeof(SECItem), + }, +}; +static const SEC_ASN1Template +realm_template[] = { + { + .kind = SEC_ASN1_GENERAL_STRING, + .offset = 0, + .sub = NULL, + .size = sizeof(SECItem), + }, +}; +static const SEC_ASN1Template +sequence_of_kerberos_string_template[] = { + { + .kind = SEC_ASN1_SEQUENCE_OF, + .offset = 0, + .sub = &kerberos_string_template, + .size = 0, + }, +}; +static const SEC_ASN1Template +principal_name_template[] = { + { + .kind = SEC_ASN1_SEQUENCE, + .offset = 0, + .sub = NULL, + .size = sizeof(struct principal_name), + }, + { + .kind = SEC_ASN1_CONTEXT_SPECIFIC | 0 | + SEC_ASN1_CONSTRUCTED | + SEC_ASN1_EXPLICIT, + .offset = offsetof(struct principal_name, name_type), + .sub = &SEC_IntegerTemplate, + .size = sizeof(SECItem), + }, + { + .kind = SEC_ASN1_CONTEXT_SPECIFIC | 1 | + SEC_ASN1_CONSTRUCTED | + SEC_ASN1_EXPLICIT, + .offset = offsetof(struct principal_name, name_string), + .sub = sequence_of_kerberos_string_template, + .size = sizeof(struct SECItem**), + }, + {0, 0, NULL, 0}, +}; +static const SEC_ASN1Template +nt_kerberos_principal_name_template[] = { + { + .kind = SEC_ASN1_SEQUENCE, + .offset = 0, + .sub = NULL, + .size = sizeof(struct kerberos_principal_name), + }, + { + .kind = SEC_ASN1_CONTEXT_SPECIFIC | 0 | + SEC_ASN1_CONSTRUCTED | + SEC_ASN1_EXPLICIT, + .offset = offsetof(struct kerberos_principal_name, realm), + .sub = &realm_template, + .size = sizeof(struct realm), + }, + { + .kind = SEC_ASN1_CONTEXT_SPECIFIC | 1 | + SEC_ASN1_CONSTRUCTED | + SEC_ASN1_EXPLICIT, + .offset = offsetof(struct kerberos_principal_name, principal_name), + .sub = &principal_name_template, + .size = sizeof(struct principal_name), + }, + {0, 0, NULL, 0}, +}; + +static int +cert_add_kpn(PLArenaPool *pool, krb5_context context, + krb5_principal **sans, int *n_sans, SECItem *name) +{ + struct kerberos_principal_name kname; + SECItem **names; + krb5_data *comps; + krb5_principal_data tmp; + unsigned long name_type; + int i, j; + + /* Decode the structure. */ + if (SEC_ASN1DecodeItem(pool, &kname, + nt_kerberos_principal_name_template, + name) != SECSuccess) { + return ENOMEM; + } + + /* Recover the name type and count the components. */ + if (SEC_ASN1DecodeInteger(&kname.principal_name.name_type, + &name_type) != SECSuccess) { + return ENOMEM; + } + names = kname.principal_name.name_string; + for (i = 0; (names != NULL) && (names[i] != NULL); i++) { + continue; + } + comps = malloc(sizeof(comps[0]) * i); + + /* Fake up a principal structure. */ + for (j = 0; j < i; j++) { + comps[i].length = names[i]->len; + comps[i].data = (char *) names[i]->data; + } + memset(&tmp, 0, sizeof(tmp)); + tmp.type = name_type; + tmp.realm.length = kname.realm.len; + tmp.realm.data = (char *) kname.realm.data; + tmp.length = i; + tmp.data = comps; + + /* Add the principal name to the list. */ + i = cert_add_princ(context, sans, n_sans, &tmp); + free(comps); + return i; +} + +static krb5_error_code +cert_retrieve_cert_sans(krb5_context context, + CERTCertificate *cert, + krb5_principal **pkinit_sans, + krb5_principal **upn_sans, + unsigned char ***kdc_hostname) +{ + PLArenaPool *pool; + CERTGeneralName name; + SECItem *ext, **encoded_names; + int i, n_pkinit_sans, n_upn_sans, n_hostnames; + + /* Pull out the extension. */ + ext = cert_get_ext_by_tag(cert, SEC_OID_X509_SUBJECT_ALT_NAME); + if (ext == NULL) { + return ENOENT; + } + + /* Split up the list of names. */ + pool = PORT_NewArena(sizeof(double)); + if (pool == NULL) { + return ENOMEM; + } + if (SEC_ASN1DecodeItem(pool, &encoded_names, SEC_SequenceOfAnyTemplate, + ext) != SECSuccess) { + PORT_FreeArena(pool, PR_TRUE); + return ENOMEM; + } + + /* Check each name in turn. */ + for (i = 0, n_pkinit_sans = 0, n_upn_sans = 0, n_hostnames = 0; + (encoded_names != NULL) && (encoded_names[i] != NULL); + i++) { + memset(&name, 0, sizeof(name)); + if (CERT_DecodeGeneralName(pool, encoded_names[i], + &name) != &name) { + continue; + } + switch (name.type) { + case certDNSName: + /* hostname, easy */ + if ((kdc_hostname != NULL) && + (cert_add_string(kdc_hostname, &n_hostnames, + name.name.other.len, + name.name.other.data) != 0)) { + PORT_FreeArena(pool, PR_TRUE); + return ENOMEM; + } + break; + case certOtherName: + /* possibly a kerberos principal name */ + if (SECITEM_ItemsAreEqual(&name.name.OthName.oid, + &pkinit_nt_principal)) { + /* Add it to the list. */ + if ((pkinit_sans != NULL) && + (cert_add_kpn(pool, context, + pkinit_sans, &n_pkinit_sans, + &name.name.OthName.name) != 0)) { + PORT_FreeArena(pool, PR_TRUE); + return ENOMEM; + } + /* If both lists are the same, fix the count. */ + if (pkinit_sans == upn_sans) { + n_upn_sans = n_pkinit_sans; + } + } else + /* possibly a user principal name */ + if (SECITEM_ItemsAreEqual(&name.name.OthName.oid, + &pkinit_nt_upn)) { + /* Add it to the list. */ + if ((upn_sans != NULL) && + (cert_add_upn(pool, context, + upn_sans, &n_upn_sans, + &name.name.OthName.name) != 0)) { + PORT_FreeArena(pool, PR_TRUE); + return ENOMEM; + } + /* If both lists are the same, fix the count. */ + if (upn_sans == pkinit_sans) { + n_pkinit_sans = n_upn_sans; + } + } + break; + default: + break; + } + } + + return 0; +} + krb5_error_code crypto_retrieve_cert_sans(krb5_context context, pkinit_plg_crypto_context plg_cryptoctx, @@ -1356,7 +1721,11 @@ crypto_retrieve_cert_sans(krb5_context context, krb5_principal **upn_sans, unsigned char ***kdc_hostname) { - return ENOSYS; + return cert_retrieve_cert_sans(context, + req_cryptoctx->peer_cert, + pkinit_sans, + upn_sans, + kdc_hostname); } krb5_error_code -- 1.7.6.4