diff --git a/kdc/pkinit.c b/kdc/pkinit.c index f876e9fe4..f9847f9c7 100644 --- a/kdc/pkinit.c +++ b/kdc/pkinit.c @@ -217,6 +217,215 @@ pk_free_client_param(krb5_context context, pk_client_params *client_params) free(client_params); } +static krb5_error_code +check_dh_params(DH *dh) +{ + /* XXX check the DH parameters come from 1st or 2nd Oeakley Group */ + return 0; +} + +static krb5_error_code +generate_dh_keyblock(krb5_context context, pk_client_params *client_params, + krb5_keyblock *reply_key) +{ + unsigned char *dh_gen_key = NULL; + krb5_keyblock key; + int dh_gen_keylen; + krb5_error_code ret; + /* XXX don't hardcode the keytype */ + krb5_enctype enctype = ETYPE_DES3_CBC_SHA1; + + memset(&key, 0, sizeof(key)); + + dh_gen_key = malloc(DH_size(client_params->dh)); + if (dh_gen_key == NULL) { + krb5_set_error_string(context, "malloc: out of memory"); + ret = ENOMEM; + goto out; + } + + if (!DH_generate_key(client_params->dh)) { + krb5_set_error_string(context, "Can't generate Diffie-Hellman " + "keys (%s)", + ERR_error_string(ERR_get_error(), NULL)); + ret = KRB5KRB_ERR_GENERIC; + goto out; + } + if (client_params->dh_public_key == NULL) { + krb5_set_error_string(context, "dh_public_key"); + ret = KRB5KRB_ERR_GENERIC; + goto out; + } + + dh_gen_keylen = DH_compute_key(dh_gen_key, + client_params->dh_public_key, + client_params->dh); + if (dh_gen_keylen == -1) { + krb5_set_error_string(context, "Can't compute Diffie-Hellman key (%s)", + ERR_error_string(ERR_get_error(), NULL)); + ret = KRB5KRB_ERR_GENERIC; + goto out; + } + + switch (enctype) { + case ETYPE_DES_CBC_CRC: + case ETYPE_DES_CBC_MD4: + case ETYPE_DES_CBC_MD5: + case ETYPE_DES3_CBC_SHA1: + case ETYPE_OLD_DES3_CBC_SHA1: { + DES_cblock *k; + + ret = krb5_generate_random_keyblock(context, enctype, &key); + if (ret) + goto out; + memset(key.keyvalue.data, 0, key.keyvalue.length); + + if (dh_gen_keylen < key.keyvalue.length) { + krb5_set_error_string(context, "Too small key generated by " + "Diffie-Hellman mechanism"); + ret = KRB5KRB_ERR_GENERIC; + goto out; + } + memcpy(key.keyvalue.data, dh_gen_key, key.keyvalue.length); + k = key.keyvalue.data; + DES_set_odd_parity(&k[0]); + switch (enctype) { + case ETYPE_DES3_CBC_SHA1: + case ETYPE_OLD_DES3_CBC_SHA1: + DES_set_odd_parity(&k[1]); + DES_set_odd_parity(&k[2]); + break; + default: + break; + } + break; + } + default: + krb5_set_error_string(context, "PKINIT DH, unsupported enctype: %d", + (int)enctype); + ret = KDC_ERROR_KEY_TOO_WEAK; + break; + } + + ret = krb5_copy_keyblock_contents(context, &key, reply_key); + + out: + if (dh_gen_key) + free(dh_gen_key); + if (key.keyvalue.data) + krb5_free_keyblock_contents(context, &key); + + return ret; +} + +static BIGNUM * +any_to_BN(krb5_context context, const char *field, heim_any *f) +{ + unsigned char *p = f->data; + ASN1_INTEGER *i; + BIGNUM *bn; + + i = d2i_ASN1_INTEGER(NULL, &p, f->length); + if (i == NULL) { + krb5_set_error_string(context, "PKINIT: failed to decode %s", field); + return NULL; + } + bn = ASN1_INTEGER_to_BN(i, NULL); + ASN1_INTEGER_free(i); + if (bn == NULL) { + krb5_set_error_string(context, + "PKINIT: failed to convert %s to bignum", + field); + return NULL; + } + return bn; +} + +static krb5_error_code +get_dh_param(krb5_context context, SubjectPublicKeyInfo *dh_key_info, + pk_client_params *client_params) +{ + DomainParameters dhparam; + DH *dh = NULL; + krb5_error_code ret; + int dhret; + + memset(&dhparam, 0, sizeof(dhparam)); + + if (oid_cmp(&dh_key_info->algorithm.algorithm, &heim_dhpublicnumber_oid)) { + krb5_set_error_string(context, + "PKINIT invalid oid in clientPublicValue"); + return KRB5_BADMSGTYPE; + } + +#if 0 + if (dh_key_info->algorithm.parameters == NULL) { + krb5_set_error_string(context, "PKINIT missing algorithm parameter " + "in clientPublicValue"); + return KRB5_BADMSGTYPE; + } +#endif + + ret = decode_DomainParameters(dh_key_info->algorithm.parameters.data, + dh_key_info->algorithm.parameters.length, + &dhparam, + NULL); + if (ret) { + krb5_set_error_string(context, "Can't decode algorithm " + "parameters in clientPublicValue"); + goto out; + } + + dh = DH_new(); + if (dh == NULL) { + krb5_set_error_string(context, "Cannot create DH structure (%s)", + ERR_error_string(ERR_get_error(), NULL)); + ret = ENOMEM; + goto out; + } + ret = KRB5_BADMSGTYPE; + dh->p = any_to_BN(context, "DH prime", &dhparam.p); + if (dh->p == NULL) + goto out; + dh->g = any_to_BN(context, "DH base", &dhparam.g); + if (dh->g == NULL) + goto out; + dh->q = any_to_BN(context, "DH p-1 factor", &dhparam.q); + if (dh->g == NULL) + goto out; + + { + heim_any glue; + glue.data = dh_key_info->subjectPublicKey.data; + glue.length = dh_key_info->subjectPublicKey.length; + + client_params->dh_public_key = any_to_BN(context, + "subjectPublicKey", + &glue); + if (client_params->dh_public_key == NULL) { + krb5_clear_error_string(context); + goto out; + } + } + + if (DH_check(dh, &dhret) != 1) { + krb5_set_error_string(context, "PKINIT DH data not ok: %s", + ERR_error_string(ERR_get_error(), NULL)); + ret = KDC_ERROR_KEY_TOO_WEAK; + goto out; + } + + client_params->dh = dh; + dh = NULL; + ret = 0; + + out: + if (dh) + DH_free(dh); + free_DomainParameters(&dhparam); + return ret; +} + krb5_error_code pk_rd_padata(krb5_context context, KDC_REQ *req, @@ -312,9 +521,9 @@ pk_rd_padata(krb5_context context, client_params->nonce = ap.pkAuthenticator.nonce; if (ap.clientPublicValue) { - ret = KRB5KRB_ERR_GENERIC; - krb5_set_error_string(context, "PKINIT DH, no support for DH"); - goto out; + ret = get_dh_param(context, ap.clientPublicValue, client_params); + if (ret) + goto out; } /* @@ -388,6 +597,7 @@ pk_rd_padata(krb5_context context, static krb5_error_code pk_mk_pa_reply_enckey(krb5_context context, pk_client_params *client_params, + const KDC_REQ *req, krb5_keyblock *reply_key, ContentInfo *content_info) { @@ -400,9 +610,9 @@ pk_mk_pa_reply_enckey(krb5_context context, krb5_data buf, sd_data, enc_sd_data; krb5_keyblock tmp_key; - krb5_enctype enctype = ENCTYPE_DES3_CBC_NONE; + krb5_enctype enctype = ETYPE_DES3_CBC_NONE; + heim_oid *enc_type_oid = NULL; X509_NAME *issuer_name; - heim_oid *enc_key_oid; size_t size; @@ -412,6 +622,15 @@ pk_mk_pa_reply_enckey(krb5_context context, memset(&tmp_key, 0, sizeof(tmp_key)); memset(&ed, 0, sizeof(ed)); + switch (enctype) { + case ETYPE_DES3_CBC_NONE: + enc_type_oid = &heim_des_ede3_cbc_oid; + break; + default: + krb5_set_error_string(context, "not support for enctype %d", enctype); + return KRB5_PROG_KEYTYPE_NOSUPP; + } + { ReplyKeyPack kp; memset(&kp, 0, sizeof(kp)); @@ -489,19 +708,20 @@ pk_mk_pa_reply_enckey(krb5_context context, ri->rid.u.issuerAndSerialNumber.issuer.length = buf.length; { + heim_oid *pk_enc_key_oid; krb5_data enc_tmp_key; ret = pk_encrypt_key(context, &tmp_key, X509_get_pubkey(client_params->certificate->cert), &enc_tmp_key, - &enc_key_oid); + &pk_enc_key_oid); if (ret) goto out; ri->encryptedKey.length = enc_tmp_key.length; ri->encryptedKey.data = enc_tmp_key.data; - ret = copy_oid(enc_key_oid, &ri->keyEncryptionAlgorithm.algorithm); + ret = copy_oid(pk_enc_key_oid, &ri->keyEncryptionAlgorithm.algorithm); if (ret) goto out; } @@ -518,7 +738,7 @@ pk_mk_pa_reply_enckey(krb5_context context, krb5_clear_error_string(context); goto out; } - ret = copy_oid(&heim_des_ede3_cbc_oid, + ret = copy_oid(enc_type_oid, &ed.encryptedContentInfo.contentEncryptionAlgorithm.algorithm); if (ret) { krb5_clear_error_string(context); @@ -562,9 +782,93 @@ pk_mk_pa_reply_enckey(krb5_context context, return ret; } +/* + * + */ + +static krb5_error_code +pk_mk_pa_reply_dh(krb5_context context, + DH *kdc_dh, + pk_client_params *client_params, + krb5_keyblock *reply_key, + ContentInfo *content_info) +{ + ASN1_INTEGER *dh_pub_key = NULL; + KDCDHKeyInfo dh_info; + krb5_error_code ret; + SignedData sd; + krb5_data buf, sd_buf; + size_t size; + + memset(&dh_info, 0, sizeof(dh_info)); + memset(&sd, 0, sizeof(sd)); + krb5_data_zero(&buf); + krb5_data_zero(&sd_buf); + + dh_pub_key = BN_to_ASN1_INTEGER(kdc_dh->pub_key, NULL); + if (dh_pub_key == NULL) { + krb5_set_error_string(context, "BN_to_ASN1_INTEGER() failed (%s)", + ERR_error_string(ERR_get_error(), NULL)); + ret = KRB5_AP_ERR_OPENSSL; + goto out; + } + + OPENSSL_ASN1_MALLOC_ENCODE(ASN1_INTEGER, buf.data, buf.length, dh_pub_key, + ret); + ASN1_INTEGER_free(dh_pub_key); + if (ret) { + krb5_set_error_string(context, "Encoding of ASN1_INTEGER failed (%s)", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + + dh_info.subjectPublicKey.length = buf.length * 8; + dh_info.subjectPublicKey.data = buf.data; + + dh_info.nonce = client_params->nonce; + + ASN1_MALLOC_ENCODE(KDCDHKeyInfo, buf.data, buf.length, &dh_info, &size, + ret); + if (ret) { + krb5_set_error_string(context, "ASN.1 encoding of " + "KdcDHKeyInfo failed (%d)", ret); + goto out; + } + if (buf.length != size) + krb5_abortx(context, "Internal ASN.1 encoder error"); + + /* + * Create the SignedData structure and sign the KdcDHKeyInfo + * filled in above + */ + + ret = _krb5_pk_create_sign(context, + &heim_pkdhkeydata_oid, + &buf, + kdc_identity, + &sd_buf); + krb5_data_free(&buf); + if (ret) + goto out; + + ret = _krb5_pk_mk_ContentInfo(context, &sd_buf, &pkcs7_signed_oid, + content_info); + krb5_data_free(&sd_buf); + + out: + free_KDCDHKeyInfo(&dh_info); + + return ret; +} + +/* + * + */ + krb5_error_code pk_mk_pa_reply(krb5_context context, pk_client_params *client_params, + const KDC_REQ *req, krb5_keyblock **reply_key, METHOD_DATA *md) { @@ -572,6 +876,7 @@ pk_mk_pa_reply(krb5_context context, PA_PK_AS_REP rep; void *buf; size_t len, size; + krb5_enctype enctype; if (!enable_pkinit) { krb5_clear_error_string(context); @@ -580,19 +885,43 @@ pk_mk_pa_reply(krb5_context context, memset(&rep, 0, sizeof(rep)); + if (req->req_body.etype.len < 1) { + ret = KRB5KRB_ERR_GENERIC; + krb5_set_error_string(context, + "No valid enctype available from client"); + goto out; + } + + enctype = req->req_body.etype.val[0]; + enctype = ETYPE_DES3_CBC_SHA1; + if (client_params->dh == NULL) { rep.element = choice_PA_PK_AS_REP_encKeyPack; - krb5_generate_random_keyblock(context, ETYPE_DES3_CBC_SHA1, + krb5_generate_random_keyblock(context, enctype, &client_params->reply_key); ret = pk_mk_pa_reply_enckey(context, client_params, + req, &client_params->reply_key, &rep.u.encKeyPack); } else { - ret = KRB5KRB_ERR_GENERIC; - krb5_set_error_string(context, "DH support not compiled in"); + rep.element = choice_PA_PK_AS_REP_dhSignedData; + + ret = check_dh_params(client_params->dh); + if (ret) + return ret; + + ret = generate_dh_keyblock(context, client_params, + &client_params->reply_key); + if (ret) + return ret; + + ret = pk_mk_pa_reply_dh(context, client_params->dh, + client_params, + &client_params->reply_key, + &rep.u.dhSignedData); } if (ret) goto out;