| /* -*- Mode: C; tab-width: 8 -*-*/ |
| /* This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| #include "crmf.h" |
| #include "crmfi.h" |
| #include "secasn1.h" |
| #include "keyhi.h" |
| #include "cryptohi.h" |
| |
| #define CRMF_DEFAULT_ALLOC_SIZE 1024U |
| |
| SECStatus |
| crmf_init_encoder_callback_arg(struct crmfEncoderArg *encoderArg, |
| SECItem *derDest) |
| { |
| derDest->data = PORT_ZNewArray(unsigned char, CRMF_DEFAULT_ALLOC_SIZE); |
| if (derDest->data == NULL) { |
| return SECFailure; |
| } |
| derDest->len = 0; |
| encoderArg->allocatedLen = CRMF_DEFAULT_ALLOC_SIZE; |
| encoderArg->buffer = derDest; |
| return SECSuccess; |
| } |
| |
| /* Caller should release or unmark the pool, instead of doing it here. |
| ** But there are NO callers of this function at present... |
| */ |
| SECStatus |
| CRMF_CertReqMsgSetRAVerifiedPOP(CRMFCertReqMsg *inCertReqMsg) |
| { |
| CRMFProofOfPossession *pop; |
| PLArenaPool *poolp; |
| void *mark; |
| |
| PORT_Assert(inCertReqMsg != NULL && inCertReqMsg->pop == NULL); |
| poolp = inCertReqMsg->poolp; |
| mark = PORT_ArenaMark(poolp); |
| if (CRMF_CertReqMsgGetPOPType(inCertReqMsg) != crmfNoPOPChoice) { |
| return SECFailure; |
| } |
| pop = PORT_ArenaZNew(poolp, CRMFProofOfPossession); |
| if (pop == NULL) { |
| goto loser; |
| } |
| pop->popUsed = crmfRAVerified; |
| pop->popChoice.raVerified.data = NULL; |
| pop->popChoice.raVerified.len = 0; |
| inCertReqMsg->pop = pop; |
| (void)SEC_ASN1EncodeItem(poolp, &(inCertReqMsg->derPOP), |
| &(pop->popChoice.raVerified), |
| CRMFRAVerifiedTemplate); |
| return SECSuccess; |
| loser: |
| PORT_ArenaRelease(poolp, mark); |
| return SECFailure; |
| } |
| |
| static SECOidTag |
| crmf_get_key_sign_tag(SECKEYPublicKey *inPubKey) |
| { |
| /* maintain backward compatibility with older |
| * implementations */ |
| if (inPubKey->keyType == rsaKey) { |
| return SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION; |
| } |
| return SEC_GetSignatureAlgorithmOidTag(inPubKey->keyType, SEC_OID_UNKNOWN); |
| } |
| |
| static SECAlgorithmID * |
| crmf_create_poposignkey_algid(PLArenaPool *poolp, |
| SECKEYPublicKey *inPubKey) |
| { |
| SECAlgorithmID *algID; |
| SECOidTag tag; |
| SECStatus rv; |
| void *mark; |
| |
| mark = PORT_ArenaMark(poolp); |
| algID = PORT_ArenaZNew(poolp, SECAlgorithmID); |
| if (algID == NULL) { |
| goto loser; |
| } |
| tag = crmf_get_key_sign_tag(inPubKey); |
| if (tag == SEC_OID_UNKNOWN) { |
| goto loser; |
| } |
| rv = SECOID_SetAlgorithmID(poolp, algID, tag, NULL); |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| PORT_ArenaUnmark(poolp, mark); |
| return algID; |
| loser: |
| PORT_ArenaRelease(poolp, mark); |
| return NULL; |
| } |
| |
| static CRMFPOPOSigningKeyInput * |
| crmf_create_poposigningkeyinput(PLArenaPool *poolp, CERTCertificate *inCert, |
| CRMFMACPasswordCallback fn, void *arg) |
| { |
| /* PSM isn't going to do this, so we'll fail here for now.*/ |
| return NULL; |
| } |
| |
| void |
| crmf_generic_encoder_callback(void *arg, const char *buf, unsigned long len, |
| int depth, SEC_ASN1EncodingPart data_kind) |
| { |
| struct crmfEncoderArg *encoderArg = (struct crmfEncoderArg *)arg; |
| unsigned char *cursor; |
| |
| if (encoderArg->buffer->len + len > encoderArg->allocatedLen) { |
| int newSize = encoderArg->buffer->len + CRMF_DEFAULT_ALLOC_SIZE; |
| void *dummy = PORT_Realloc(encoderArg->buffer->data, newSize); |
| if (dummy == NULL) { |
| /* I really want to return an error code here */ |
| PORT_Assert(0); |
| return; |
| } |
| encoderArg->buffer->data = dummy; |
| encoderArg->allocatedLen = newSize; |
| } |
| cursor = &(encoderArg->buffer->data[encoderArg->buffer->len]); |
| if (len) { |
| PORT_Memcpy(cursor, buf, len); |
| } |
| encoderArg->buffer->len += len; |
| } |
| |
| static SECStatus |
| crmf_encode_certreq(CRMFCertRequest *inCertReq, SECItem *derDest) |
| { |
| struct crmfEncoderArg encoderArg; |
| SECStatus rv; |
| |
| rv = crmf_init_encoder_callback_arg(&encoderArg, derDest); |
| if (rv != SECSuccess) { |
| return SECFailure; |
| } |
| return SEC_ASN1Encode(inCertReq, CRMFCertRequestTemplate, |
| crmf_generic_encoder_callback, &encoderArg); |
| } |
| |
| static SECStatus |
| crmf_sign_certreq(PLArenaPool *poolp, |
| CRMFPOPOSigningKey *crmfSignKey, |
| CRMFCertRequest *certReq, |
| SECKEYPrivateKey *inKey, |
| SECAlgorithmID *inAlgId) |
| { |
| SECItem derCertReq = { siBuffer, NULL, 0 }; |
| SECItem certReqSig = { siBuffer, NULL, 0 }; |
| SECStatus rv = SECSuccess; |
| |
| rv = crmf_encode_certreq(certReq, &derCertReq); |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| rv = SEC_SignData(&certReqSig, derCertReq.data, derCertReq.len, |
| inKey, SECOID_GetAlgorithmTag(inAlgId)); |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| |
| /* Now make it a part of the POPOSigningKey */ |
| rv = SECITEM_CopyItem(poolp, &(crmfSignKey->signature), &certReqSig); |
| /* Convert this length to number of bits */ |
| crmfSignKey->signature.len <<= 3; |
| |
| loser: |
| if (derCertReq.data != NULL) { |
| PORT_Free(derCertReq.data); |
| } |
| if (certReqSig.data != NULL) { |
| PORT_Free(certReqSig.data); |
| } |
| return rv; |
| } |
| |
| static SECStatus |
| crmf_create_poposignkey(PLArenaPool *poolp, |
| CRMFCertReqMsg *inCertReqMsg, |
| CRMFPOPOSigningKeyInput *signKeyInput, |
| SECKEYPrivateKey *inPrivKey, |
| SECAlgorithmID *inAlgID, |
| CRMFPOPOSigningKey *signKey) |
| { |
| CRMFCertRequest *certReq; |
| void *mark; |
| PRBool useSignKeyInput; |
| SECStatus rv; |
| |
| PORT_Assert(inCertReqMsg != NULL && inCertReqMsg->certReq != NULL); |
| mark = PORT_ArenaMark(poolp); |
| if (signKey == NULL) { |
| goto loser; |
| } |
| certReq = inCertReqMsg->certReq; |
| useSignKeyInput = !(CRMF_DoesRequestHaveField(certReq, crmfSubject) && |
| CRMF_DoesRequestHaveField(certReq, crmfPublicKey)); |
| |
| if (useSignKeyInput) { |
| goto loser; |
| } else { |
| rv = crmf_sign_certreq(poolp, signKey, certReq, inPrivKey, inAlgID); |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| } |
| PORT_ArenaUnmark(poolp, mark); |
| return SECSuccess; |
| loser: |
| PORT_ArenaRelease(poolp, mark); |
| return SECFailure; |
| } |
| |
| SECStatus |
| CRMF_CertReqMsgSetSignaturePOP(CRMFCertReqMsg *inCertReqMsg, |
| SECKEYPrivateKey *inPrivKey, |
| SECKEYPublicKey *inPubKey, |
| CERTCertificate *inCertForInput, |
| CRMFMACPasswordCallback fn, |
| void *arg) |
| { |
| SECAlgorithmID *algID; |
| PLArenaPool *poolp; |
| SECItem derTemp = { siBuffer, NULL, 0 }; |
| void *mark; |
| SECStatus rv; |
| CRMFPOPOSigningKeyInput *signKeyInput = NULL; |
| CRMFCertRequest *certReq; |
| CRMFProofOfPossession *pop; |
| struct crmfEncoderArg encoderArg; |
| |
| PORT_Assert(inCertReqMsg != NULL && inCertReqMsg->certReq != NULL && |
| inCertReqMsg->pop == NULL); |
| certReq = inCertReqMsg->certReq; |
| if (CRMF_CertReqMsgGetPOPType(inCertReqMsg) != crmfNoPOPChoice || |
| !CRMF_DoesRequestHaveField(certReq, crmfPublicKey)) { |
| return SECFailure; |
| } |
| poolp = inCertReqMsg->poolp; |
| mark = PORT_ArenaMark(poolp); |
| algID = crmf_create_poposignkey_algid(poolp, inPubKey); |
| |
| if (!CRMF_DoesRequestHaveField(certReq, crmfSubject)) { |
| signKeyInput = crmf_create_poposigningkeyinput(poolp, inCertForInput, |
| fn, arg); |
| if (signKeyInput == NULL) { |
| goto loser; |
| } |
| } |
| |
| pop = PORT_ArenaZNew(poolp, CRMFProofOfPossession); |
| if (pop == NULL) { |
| goto loser; |
| } |
| |
| rv = crmf_create_poposignkey(poolp, inCertReqMsg, |
| signKeyInput, inPrivKey, algID, |
| &(pop->popChoice.signature)); |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| |
| pop->popUsed = crmfSignature; |
| pop->popChoice.signature.algorithmIdentifier = algID; |
| inCertReqMsg->pop = pop; |
| |
| rv = crmf_init_encoder_callback_arg(&encoderArg, &derTemp); |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| rv = SEC_ASN1Encode(&pop->popChoice.signature, |
| CRMFPOPOSigningKeyTemplate, |
| crmf_generic_encoder_callback, &encoderArg); |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| rv = SECITEM_CopyItem(poolp, &(inCertReqMsg->derPOP), &derTemp); |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| PORT_Free(derTemp.data); |
| PORT_ArenaUnmark(poolp, mark); |
| return SECSuccess; |
| |
| loser: |
| PORT_ArenaRelease(poolp, mark); |
| if (derTemp.data != NULL) { |
| PORT_Free(derTemp.data); |
| } |
| return SECFailure; |
| } |
| |
| static const SEC_ASN1Template * |
| crmf_get_popoprivkey_subtemplate(CRMFPOPOPrivKey *inPrivKey) |
| { |
| const SEC_ASN1Template *retTemplate = NULL; |
| |
| switch (inPrivKey->messageChoice) { |
| case crmfThisMessage: |
| retTemplate = CRMFThisMessageTemplate; |
| break; |
| case crmfSubsequentMessage: |
| retTemplate = CRMFSubsequentMessageTemplate; |
| break; |
| case crmfDHMAC: |
| retTemplate = CRMFDHMACTemplate; |
| break; |
| default: |
| retTemplate = NULL; |
| } |
| return retTemplate; |
| } |
| |
| static SECStatus |
| crmf_encode_popoprivkey(PLArenaPool *poolp, |
| CRMFCertReqMsg *inCertReqMsg, |
| CRMFPOPOPrivKey *popoPrivKey, |
| const SEC_ASN1Template *privKeyTemplate) |
| { |
| struct crmfEncoderArg encoderArg; |
| SECItem derTemp = { siBuffer, NULL, 0 }; |
| SECStatus rv; |
| void *mark; |
| const SEC_ASN1Template *subDerTemplate; |
| |
| mark = PORT_ArenaMark(poolp); |
| rv = crmf_init_encoder_callback_arg(&encoderArg, &derTemp); |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| subDerTemplate = crmf_get_popoprivkey_subtemplate(popoPrivKey); |
| /* We've got a union, so a pointer to one item is a pointer to |
| * all the items in the union. |
| */ |
| rv = SEC_ASN1Encode(&popoPrivKey->message.thisMessage, |
| subDerTemplate, |
| crmf_generic_encoder_callback, &encoderArg); |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| if (encoderArg.allocatedLen > derTemp.len + 2) { |
| void *dummy = PORT_Realloc(derTemp.data, derTemp.len + 2); |
| if (dummy == NULL) { |
| goto loser; |
| } |
| derTemp.data = dummy; |
| } |
| PORT_Memmove(&derTemp.data[2], &derTemp.data[0], derTemp.len); |
| /* I couldn't figure out how to get the ASN1 encoder to implicitly |
| * tag an implicitly tagged der blob. So I'm putting in the outter- |
| * most tag myself. -javi |
| */ |
| derTemp.data[0] = (unsigned char)privKeyTemplate->kind; |
| derTemp.data[1] = (unsigned char)derTemp.len; |
| derTemp.len += 2; |
| rv = SECITEM_CopyItem(poolp, &inCertReqMsg->derPOP, &derTemp); |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| PORT_Free(derTemp.data); |
| PORT_ArenaUnmark(poolp, mark); |
| return SECSuccess; |
| loser: |
| PORT_ArenaRelease(poolp, mark); |
| if (derTemp.data) { |
| PORT_Free(derTemp.data); |
| } |
| return SECFailure; |
| } |
| |
| static const SEC_ASN1Template * |
| crmf_get_template_for_privkey(CRMFPOPChoice inChoice) |
| { |
| switch (inChoice) { |
| case crmfKeyAgreement: |
| return CRMFPOPOKeyAgreementTemplate; |
| case crmfKeyEncipherment: |
| return CRMFPOPOKeyEnciphermentTemplate; |
| default: |
| break; |
| } |
| return NULL; |
| } |
| |
| static SECStatus |
| crmf_add_privkey_thismessage(CRMFCertReqMsg *inCertReqMsg, SECItem *encPrivKey, |
| CRMFPOPChoice inChoice) |
| { |
| PLArenaPool *poolp; |
| void *mark; |
| CRMFPOPOPrivKey *popoPrivKey; |
| CRMFProofOfPossession *pop; |
| SECStatus rv; |
| |
| PORT_Assert(inCertReqMsg != NULL && encPrivKey != NULL); |
| poolp = inCertReqMsg->poolp; |
| mark = PORT_ArenaMark(poolp); |
| pop = PORT_ArenaZNew(poolp, CRMFProofOfPossession); |
| if (pop == NULL) { |
| goto loser; |
| } |
| pop->popUsed = inChoice; |
| /* popChoice is a union, so getting a pointer to one |
| * field gives me a pointer to the other fields as |
| * well. This in essence points to both |
| * pop->popChoice.keyEncipherment and |
| * pop->popChoice.keyAgreement |
| */ |
| popoPrivKey = &pop->popChoice.keyEncipherment; |
| |
| rv = SECITEM_CopyItem(poolp, &(popoPrivKey->message.thisMessage), |
| encPrivKey); |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| popoPrivKey->message.thisMessage.len <<= 3; |
| popoPrivKey->messageChoice = crmfThisMessage; |
| inCertReqMsg->pop = pop; |
| rv = crmf_encode_popoprivkey(poolp, inCertReqMsg, popoPrivKey, |
| crmf_get_template_for_privkey(inChoice)); |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| PORT_ArenaUnmark(poolp, mark); |
| return SECSuccess; |
| |
| loser: |
| PORT_ArenaRelease(poolp, mark); |
| return SECFailure; |
| } |
| |
| static SECStatus |
| crmf_add_privkey_dhmac(CRMFCertReqMsg *inCertReqMsg, SECItem *dhmac, |
| CRMFPOPChoice inChoice) |
| { |
| PLArenaPool *poolp; |
| void *mark; |
| CRMFPOPOPrivKey *popoPrivKey; |
| CRMFProofOfPossession *pop; |
| SECStatus rv; |
| |
| PORT_Assert(inCertReqMsg != NULL && dhmac != NULL); |
| poolp = inCertReqMsg->poolp; |
| mark = PORT_ArenaMark(poolp); |
| pop = PORT_ArenaZNew(poolp, CRMFProofOfPossession); |
| if (pop == NULL) { |
| goto loser; |
| } |
| pop->popUsed = inChoice; |
| popoPrivKey = &pop->popChoice.keyAgreement; |
| |
| rv = SECITEM_CopyItem(poolp, &(popoPrivKey->message.dhMAC), |
| dhmac); |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| popoPrivKey->message.dhMAC.len <<= 3; |
| popoPrivKey->messageChoice = crmfDHMAC; |
| inCertReqMsg->pop = pop; |
| rv = crmf_encode_popoprivkey(poolp, inCertReqMsg, popoPrivKey, |
| crmf_get_template_for_privkey(inChoice)); |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| PORT_ArenaUnmark(poolp, mark); |
| return SECSuccess; |
| |
| loser: |
| PORT_ArenaRelease(poolp, mark); |
| return SECFailure; |
| } |
| |
| static SECStatus |
| crmf_add_privkey_subseqmessage(CRMFCertReqMsg *inCertReqMsg, |
| CRMFSubseqMessOptions subsequentMessage, |
| CRMFPOPChoice inChoice) |
| { |
| void *mark; |
| PLArenaPool *poolp; |
| CRMFProofOfPossession *pop; |
| CRMFPOPOPrivKey *popoPrivKey; |
| SECStatus rv; |
| const SEC_ASN1Template *privKeyTemplate; |
| |
| if (subsequentMessage == crmfNoSubseqMess) { |
| return SECFailure; |
| } |
| poolp = inCertReqMsg->poolp; |
| mark = PORT_ArenaMark(poolp); |
| pop = PORT_ArenaZNew(poolp, CRMFProofOfPossession); |
| if (pop == NULL) { |
| goto loser; |
| } |
| |
| pop->popUsed = inChoice; |
| /* |
| * We have a union, so a pointer to one member of the union |
| * is also a member to another member of that same union. |
| */ |
| popoPrivKey = &pop->popChoice.keyEncipherment; |
| |
| switch (subsequentMessage) { |
| case crmfEncrCert: |
| rv = crmf_encode_integer(poolp, |
| &(popoPrivKey->message.subsequentMessage), |
| 0); |
| break; |
| case crmfChallengeResp: |
| rv = crmf_encode_integer(poolp, |
| &(popoPrivKey->message.subsequentMessage), |
| 1); |
| break; |
| default: |
| goto loser; |
| } |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| popoPrivKey->messageChoice = crmfSubsequentMessage; |
| privKeyTemplate = crmf_get_template_for_privkey(inChoice); |
| inCertReqMsg->pop = pop; |
| rv = crmf_encode_popoprivkey(poolp, inCertReqMsg, popoPrivKey, |
| privKeyTemplate); |
| |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| PORT_ArenaUnmark(poolp, mark); |
| return SECSuccess; |
| loser: |
| PORT_ArenaRelease(poolp, mark); |
| return SECFailure; |
| } |
| |
| SECStatus |
| CRMF_CertReqMsgSetKeyEnciphermentPOP(CRMFCertReqMsg *inCertReqMsg, |
| CRMFPOPOPrivKeyChoice inKeyChoice, |
| CRMFSubseqMessOptions subseqMess, |
| SECItem *encPrivKey) |
| { |
| SECStatus rv; |
| |
| PORT_Assert(inCertReqMsg != NULL && inCertReqMsg->pop == NULL); |
| if (CRMF_CertReqMsgGetPOPType(inCertReqMsg) != crmfNoPOPChoice) { |
| return SECFailure; |
| } |
| switch (inKeyChoice) { |
| case crmfThisMessage: |
| rv = crmf_add_privkey_thismessage(inCertReqMsg, encPrivKey, |
| crmfKeyEncipherment); |
| break; |
| case crmfSubsequentMessage: |
| rv = crmf_add_privkey_subseqmessage(inCertReqMsg, subseqMess, |
| crmfKeyEncipherment); |
| break; |
| case crmfDHMAC: |
| default: |
| rv = SECFailure; |
| } |
| return rv; |
| } |
| |
| SECStatus |
| CRMF_CertReqMsgSetKeyAgreementPOP(CRMFCertReqMsg *inCertReqMsg, |
| CRMFPOPOPrivKeyChoice inKeyChoice, |
| CRMFSubseqMessOptions subseqMess, |
| SECItem *encPrivKey) |
| { |
| SECStatus rv; |
| |
| PORT_Assert(inCertReqMsg != NULL && inCertReqMsg->pop == NULL); |
| switch (inKeyChoice) { |
| case crmfThisMessage: |
| rv = crmf_add_privkey_thismessage(inCertReqMsg, encPrivKey, |
| crmfKeyAgreement); |
| break; |
| case crmfSubsequentMessage: |
| rv = crmf_add_privkey_subseqmessage(inCertReqMsg, subseqMess, |
| crmfKeyAgreement); |
| break; |
| case crmfDHMAC: |
| /* In this case encPrivKey should be the calculated dhMac |
| * as specified in RFC 2511 */ |
| rv = crmf_add_privkey_dhmac(inCertReqMsg, encPrivKey, |
| crmfKeyAgreement); |
| break; |
| default: |
| rv = SECFailure; |
| } |
| return rv; |
| } |