diff options
Diffstat (limited to '')
-rw-r--r-- | security/nss/lib/certhigh/ocspsig.c | 597 |
1 files changed, 597 insertions, 0 deletions
diff --git a/security/nss/lib/certhigh/ocspsig.c b/security/nss/lib/certhigh/ocspsig.c new file mode 100644 index 0000000000..94606baf56 --- /dev/null +++ b/security/nss/lib/certhigh/ocspsig.c @@ -0,0 +1,597 @@ +/* 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 "plarena.h" + +#include "seccomon.h" +#include "secitem.h" +#include "secasn1.h" +#include "secder.h" +#include "cert.h" +#include "secerr.h" +#include "secoid.h" +#include "sechash.h" +#include "keyhi.h" +#include "cryptohi.h" +#include "ocsp.h" +#include "ocspti.h" +#include "ocspi.h" +#include "pk11pub.h" + +extern const SEC_ASN1Template ocsp_ResponderIDByNameTemplate[]; +extern const SEC_ASN1Template ocsp_ResponderIDByKeyTemplate[]; +extern const SEC_ASN1Template ocsp_OCSPResponseTemplate[]; + +ocspCertStatus * +ocsp_CreateCertStatus(PLArenaPool *arena, + ocspCertStatusType status, + PRTime revocationTime) +{ + ocspCertStatus *cs; + + if (!arena) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + switch (status) { + case ocspCertStatus_good: + case ocspCertStatus_unknown: + case ocspCertStatus_revoked: + break; + default: + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + cs = PORT_ArenaZNew(arena, ocspCertStatus); + if (!cs) + return NULL; + cs->certStatusType = status; + switch (status) { + case ocspCertStatus_good: + cs->certStatusInfo.goodInfo = SECITEM_AllocItem(arena, NULL, 0); + if (!cs->certStatusInfo.goodInfo) + return NULL; + break; + case ocspCertStatus_unknown: + cs->certStatusInfo.unknownInfo = SECITEM_AllocItem(arena, NULL, 0); + if (!cs->certStatusInfo.unknownInfo) + return NULL; + break; + case ocspCertStatus_revoked: + cs->certStatusInfo.revokedInfo = + PORT_ArenaZNew(arena, ocspRevokedInfo); + if (!cs->certStatusInfo.revokedInfo) + return NULL; + cs->certStatusInfo.revokedInfo->revocationReason = + SECITEM_AllocItem(arena, NULL, 0); + if (!cs->certStatusInfo.revokedInfo->revocationReason) + return NULL; + if (DER_TimeToGeneralizedTimeArena(arena, + &cs->certStatusInfo.revokedInfo->revocationTime, + revocationTime) != + SECSuccess) + return NULL; + break; + default: + PORT_Assert(PR_FALSE); + } + return cs; +} + +static const SEC_ASN1Template mySEC_EnumeratedTemplate[] = { + { SEC_ASN1_ENUMERATED, 0, NULL, sizeof(SECItem) } +}; + +static const SEC_ASN1Template mySEC_PointerToEnumeratedTemplate[] = { + { SEC_ASN1_POINTER, 0, mySEC_EnumeratedTemplate } +}; + +static const SEC_ASN1Template ocsp_EncodeRevokedInfoTemplate[] = { + { SEC_ASN1_GENERALIZED_TIME, + offsetof(ocspRevokedInfo, revocationTime) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | + SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(ocspRevokedInfo, revocationReason), + mySEC_PointerToEnumeratedTemplate }, + { 0 } +}; + +static const SEC_ASN1Template ocsp_PointerToEncodeRevokedInfoTemplate[] = { + { SEC_ASN1_POINTER, 0, + ocsp_EncodeRevokedInfoTemplate } +}; + +static const SEC_ASN1Template mySEC_NullTemplate[] = { + { SEC_ASN1_NULL, 0, NULL, sizeof(SECItem) } +}; + +static const SEC_ASN1Template ocsp_CertStatusTemplate[] = { + { SEC_ASN1_CHOICE, offsetof(ocspCertStatus, certStatusType), + 0, sizeof(ocspCertStatus) }, + { SEC_ASN1_CONTEXT_SPECIFIC | 0, + 0, mySEC_NullTemplate, ocspCertStatus_good }, + { SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED | + SEC_ASN1_CONTEXT_SPECIFIC | 1, + offsetof(ocspCertStatus, certStatusInfo.revokedInfo), + ocsp_PointerToEncodeRevokedInfoTemplate, ocspCertStatus_revoked }, + { SEC_ASN1_CONTEXT_SPECIFIC | 2, + 0, mySEC_NullTemplate, ocspCertStatus_unknown }, + { 0 } +}; + +static const SEC_ASN1Template mySECOID_AlgorithmIDTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(SECAlgorithmID) }, + { SEC_ASN1_OBJECT_ID, + offsetof(SECAlgorithmID, algorithm) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_ANY, + offsetof(SECAlgorithmID, parameters) }, + { 0 } +}; + +static const SEC_ASN1Template mySEC_AnyTemplate[] = { + { SEC_ASN1_ANY | SEC_ASN1_MAY_STREAM, 0, NULL, sizeof(SECItem) } +}; + +static const SEC_ASN1Template mySEC_SequenceOfAnyTemplate[] = { + { SEC_ASN1_SEQUENCE_OF, 0, mySEC_AnyTemplate } +}; + +static const SEC_ASN1Template mySEC_PointerToSequenceOfAnyTemplate[] = { + { SEC_ASN1_POINTER, 0, mySEC_SequenceOfAnyTemplate } +}; + +static const SEC_ASN1Template mySEC_IntegerTemplate[] = { + { SEC_ASN1_INTEGER, 0, NULL, sizeof(SECItem) } +}; + +static const SEC_ASN1Template mySEC_PointerToIntegerTemplate[] = { + { SEC_ASN1_POINTER, 0, mySEC_IntegerTemplate } +}; + +static const SEC_ASN1Template mySEC_GeneralizedTimeTemplate[] = { + { SEC_ASN1_GENERALIZED_TIME | SEC_ASN1_MAY_STREAM, 0, NULL, sizeof(SECItem) } +}; + +static const SEC_ASN1Template mySEC_PointerToGeneralizedTimeTemplate[] = { + { SEC_ASN1_POINTER, 0, mySEC_GeneralizedTimeTemplate } +}; + +static const SEC_ASN1Template ocsp_myCertIDTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(CERTOCSPCertID) }, + { SEC_ASN1_INLINE, + offsetof(CERTOCSPCertID, hashAlgorithm), + mySECOID_AlgorithmIDTemplate }, + { SEC_ASN1_OCTET_STRING, + offsetof(CERTOCSPCertID, issuerNameHash) }, + { SEC_ASN1_OCTET_STRING, + offsetof(CERTOCSPCertID, issuerKeyHash) }, + { SEC_ASN1_INTEGER, + offsetof(CERTOCSPCertID, serialNumber) }, + { 0 } +}; + +static const SEC_ASN1Template myCERT_CertExtensionTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(CERTCertExtension) }, + { SEC_ASN1_OBJECT_ID, + offsetof(CERTCertExtension, id) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_BOOLEAN, /* XXX DER_DEFAULT */ + offsetof(CERTCertExtension, critical) }, + { SEC_ASN1_OCTET_STRING, + offsetof(CERTCertExtension, value) }, + { 0 } +}; + +static const SEC_ASN1Template myCERT_SequenceOfCertExtensionTemplate[] = { + { SEC_ASN1_SEQUENCE_OF, 0, myCERT_CertExtensionTemplate } +}; + +static const SEC_ASN1Template myCERT_PointerToSequenceOfCertExtensionTemplate[] = { + { SEC_ASN1_POINTER, 0, myCERT_SequenceOfCertExtensionTemplate } +}; + +static const SEC_ASN1Template ocsp_mySingleResponseTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(CERTOCSPSingleResponse) }, + { SEC_ASN1_POINTER, + offsetof(CERTOCSPSingleResponse, certID), + ocsp_myCertIDTemplate }, + { SEC_ASN1_ANY, + offsetof(CERTOCSPSingleResponse, derCertStatus) }, + { SEC_ASN1_GENERALIZED_TIME, + offsetof(CERTOCSPSingleResponse, thisUpdate) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | + SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(CERTOCSPSingleResponse, nextUpdate), + mySEC_PointerToGeneralizedTimeTemplate }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | + SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1, + offsetof(CERTOCSPSingleResponse, singleExtensions), + myCERT_PointerToSequenceOfCertExtensionTemplate }, + { 0 } +}; + +static const SEC_ASN1Template ocsp_myResponseDataTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(ocspResponseData) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | /* XXX DER_DEFAULT */ + SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(ocspResponseData, version), + mySEC_PointerToIntegerTemplate }, + { SEC_ASN1_ANY, + offsetof(ocspResponseData, derResponderID) }, + { SEC_ASN1_GENERALIZED_TIME, + offsetof(ocspResponseData, producedAt) }, + { SEC_ASN1_SEQUENCE_OF, + offsetof(ocspResponseData, responses), + ocsp_mySingleResponseTemplate }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | + SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1, + offsetof(ocspResponseData, responseExtensions), + myCERT_PointerToSequenceOfCertExtensionTemplate }, + { 0 } +}; + +static const SEC_ASN1Template ocsp_EncodeBasicOCSPResponseTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(ocspBasicOCSPResponse) }, + { SEC_ASN1_POINTER, + offsetof(ocspBasicOCSPResponse, tbsResponseData), + ocsp_myResponseDataTemplate }, + { SEC_ASN1_INLINE, + offsetof(ocspBasicOCSPResponse, responseSignature.signatureAlgorithm), + mySECOID_AlgorithmIDTemplate }, + { SEC_ASN1_BIT_STRING, + offsetof(ocspBasicOCSPResponse, responseSignature.signature) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | + SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(ocspBasicOCSPResponse, responseSignature.derCerts), + mySEC_PointerToSequenceOfAnyTemplate }, + { 0 } +}; + +static CERTOCSPSingleResponse * +ocsp_CreateSingleResponse(PLArenaPool *arena, + CERTOCSPCertID *id, ocspCertStatus *status, + PRTime thisUpdate, const PRTime *nextUpdate) +{ + CERTOCSPSingleResponse *sr; + + if (!arena || !id || !status) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + sr = PORT_ArenaZNew(arena, CERTOCSPSingleResponse); + if (!sr) + return NULL; + sr->arena = arena; + sr->certID = id; + sr->certStatus = status; + if (DER_TimeToGeneralizedTimeArena(arena, &sr->thisUpdate, thisUpdate) != + SECSuccess) + return NULL; + sr->nextUpdate = NULL; + if (nextUpdate) { + sr->nextUpdate = SECITEM_AllocItem(arena, NULL, 0); + if (!sr->nextUpdate) + return NULL; + if (DER_TimeToGeneralizedTimeArena(arena, sr->nextUpdate, *nextUpdate) != + SECSuccess) + return NULL; + } + + sr->singleExtensions = PORT_ArenaNewArray(arena, CERTCertExtension *, 1); + if (!sr->singleExtensions) + return NULL; + + sr->singleExtensions[0] = NULL; + + if (!SEC_ASN1EncodeItem(arena, &sr->derCertStatus, + status, ocsp_CertStatusTemplate)) + return NULL; + + return sr; +} + +CERTOCSPSingleResponse * +CERT_CreateOCSPSingleResponseGood(PLArenaPool *arena, + CERTOCSPCertID *id, + PRTime thisUpdate, + const PRTime *nextUpdate) +{ + ocspCertStatus *cs; + if (!arena) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + cs = ocsp_CreateCertStatus(arena, ocspCertStatus_good, 0); + if (!cs) + return NULL; + return ocsp_CreateSingleResponse(arena, id, cs, thisUpdate, nextUpdate); +} + +CERTOCSPSingleResponse * +CERT_CreateOCSPSingleResponseUnknown(PLArenaPool *arena, + CERTOCSPCertID *id, + PRTime thisUpdate, + const PRTime *nextUpdate) +{ + ocspCertStatus *cs; + if (!arena) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + cs = ocsp_CreateCertStatus(arena, ocspCertStatus_unknown, 0); + if (!cs) + return NULL; + return ocsp_CreateSingleResponse(arena, id, cs, thisUpdate, nextUpdate); +} + +CERTOCSPSingleResponse * +CERT_CreateOCSPSingleResponseRevoked( + PLArenaPool *arena, + CERTOCSPCertID *id, + PRTime thisUpdate, + const PRTime *nextUpdate, + PRTime revocationTime, + const CERTCRLEntryReasonCode *revocationReason) +{ + ocspCertStatus *cs; + /* revocationReason is not yet supported, so it must be NULL. */ + if (!arena || revocationReason) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + cs = ocsp_CreateCertStatus(arena, ocspCertStatus_revoked, revocationTime); + if (!cs) + return NULL; + return ocsp_CreateSingleResponse(arena, id, cs, thisUpdate, nextUpdate); +} + +/* responderCert == 0 means: + * create a response with an invalid signature (for testing purposes) */ +SECItem * +CERT_CreateEncodedOCSPSuccessResponse( + PLArenaPool *arena, + CERTCertificate *responderCert, + CERTOCSPResponderIDType responderIDType, + PRTime producedAt, + CERTOCSPSingleResponse **responses, + void *wincx) +{ + PLArenaPool *tmpArena; + ocspResponseData *rd = NULL; + ocspResponderID *rid = NULL; + const SEC_ASN1Template *responderIDTemplate = NULL; + ocspBasicOCSPResponse *br = NULL; + ocspResponseBytes *rb = NULL; + CERTOCSPResponse *response = NULL; + + SECOidTag algID; + SECOidData *od = NULL; + SECKEYPrivateKey *privKey = NULL; + SECItem *result = NULL; + + if (!arena || !responses) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + if (responderIDType != ocspResponderID_byName && + responderIDType != ocspResponderID_byKey) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + tmpArena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (!tmpArena) + return NULL; + + rd = PORT_ArenaZNew(tmpArena, ocspResponseData); + if (!rd) + goto done; + rid = PORT_ArenaZNew(tmpArena, ocspResponderID); + if (!rid) + goto done; + br = PORT_ArenaZNew(tmpArena, ocspBasicOCSPResponse); + if (!br) + goto done; + rb = PORT_ArenaZNew(tmpArena, ocspResponseBytes); + if (!rb) + goto done; + response = PORT_ArenaZNew(tmpArena, CERTOCSPResponse); + if (!response) + goto done; + + rd->version.data = NULL; + rd->version.len = 0; + rd->responseExtensions = NULL; + rd->responses = responses; + if (DER_TimeToGeneralizedTimeArena(tmpArena, &rd->producedAt, producedAt) != + SECSuccess) + goto done; + + if (!responderCert) { + /* use invalid signature for testing purposes */ + unsigned char dummyChar = 'd'; + SECItem dummy; + + dummy.len = 1; + dummy.data = &dummyChar; + + /* it's easier to produdce a keyHash out of nowhere, + * than to produce an encoded subject, + * so for our dummy response we always use byKey + */ + + rid->responderIDType = ocspResponderID_byKey; + if (!ocsp_DigestValue(tmpArena, SEC_OID_SHA1, &rid->responderIDValue.keyHash, + &dummy)) + goto done; + + if (!SEC_ASN1EncodeItem(tmpArena, &rd->derResponderID, rid, + ocsp_ResponderIDByKeyTemplate)) + goto done; + + br->tbsResponseData = rd; + + if (!SEC_ASN1EncodeItem(tmpArena, &br->tbsResponseDataDER, br->tbsResponseData, + ocsp_myResponseDataTemplate)) + goto done; + + br->responseSignature.derCerts = PORT_ArenaNewArray(tmpArena, SECItem *, 1); + if (!br->responseSignature.derCerts) + goto done; + br->responseSignature.derCerts[0] = NULL; + + algID = SEC_GetSignatureAlgorithmOidTag(rsaKey, SEC_OID_SHA1); + if (algID == SEC_OID_UNKNOWN) + goto done; + + /* match the regular signature code, which doesn't use the arena */ + if (!SECITEM_AllocItem(NULL, &br->responseSignature.signature, 1)) + goto done; + PORT_Memcpy(br->responseSignature.signature.data, &dummyChar, 1); + + /* convert len-in-bytes to len-in-bits */ + br->responseSignature.signature.len = br->responseSignature.signature.len << 3; + } else { + rid->responderIDType = responderIDType; + if (responderIDType == ocspResponderID_byName) { + responderIDTemplate = ocsp_ResponderIDByNameTemplate; + if (CERT_CopyName(tmpArena, &rid->responderIDValue.name, + &responderCert->subject) != SECSuccess) + goto done; + } else { + responderIDTemplate = ocsp_ResponderIDByKeyTemplate; + if (!CERT_GetSubjectPublicKeyDigest(tmpArena, responderCert, + SEC_OID_SHA1, &rid->responderIDValue.keyHash)) + goto done; + } + + if (!SEC_ASN1EncodeItem(tmpArena, &rd->derResponderID, rid, + responderIDTemplate)) + goto done; + + br->tbsResponseData = rd; + + if (!SEC_ASN1EncodeItem(tmpArena, &br->tbsResponseDataDER, br->tbsResponseData, + ocsp_myResponseDataTemplate)) + goto done; + + br->responseSignature.derCerts = PORT_ArenaNewArray(tmpArena, SECItem *, 1); + if (!br->responseSignature.derCerts) + goto done; + br->responseSignature.derCerts[0] = NULL; + + privKey = PK11_FindKeyByAnyCert(responderCert, wincx); + if (!privKey) + goto done; + + algID = SEC_GetSignatureAlgorithmOidTag(privKey->keyType, SEC_OID_SHA1); + if (algID == SEC_OID_UNKNOWN) + goto done; + + if (SEC_SignData(&br->responseSignature.signature, + br->tbsResponseDataDER.data, br->tbsResponseDataDER.len, + privKey, algID) != + SECSuccess) + goto done; + + /* convert len-in-bytes to len-in-bits */ + br->responseSignature.signature.len = br->responseSignature.signature.len << 3; + + /* br->responseSignature.signature wasn't allocated from arena, + * we must free it when done. */ + } + + if (SECOID_SetAlgorithmID(tmpArena, &br->responseSignature.signatureAlgorithm, algID, 0) != + SECSuccess) + goto done; + + if (!SEC_ASN1EncodeItem(tmpArena, &rb->response, br, + ocsp_EncodeBasicOCSPResponseTemplate)) + goto done; + + rb->responseTypeTag = SEC_OID_PKIX_OCSP_BASIC_RESPONSE; + + od = SECOID_FindOIDByTag(rb->responseTypeTag); + if (!od) + goto done; + + rb->responseType = od->oid; + rb->decodedResponse.basic = br; + + response->arena = tmpArena; + response->responseBytes = rb; + response->statusValue = ocspResponse_successful; + + if (!SEC_ASN1EncodeInteger(tmpArena, &response->responseStatus, + response->statusValue)) + goto done; + + result = SEC_ASN1EncodeItem(arena, NULL, response, ocsp_OCSPResponseTemplate); + +done: + if (privKey) + SECKEY_DestroyPrivateKey(privKey); + if (br && br->responseSignature.signature.data) + SECITEM_FreeItem(&br->responseSignature.signature, PR_FALSE); + PORT_FreeArena(tmpArena, PR_FALSE); + + return result; +} + +static const SEC_ASN1Template ocsp_OCSPErrorResponseTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(CERTOCSPResponse) }, + { SEC_ASN1_ENUMERATED, + offsetof(CERTOCSPResponse, responseStatus) }, + { 0, 0, + mySEC_NullTemplate }, + { 0 } +}; + +SECItem * +CERT_CreateEncodedOCSPErrorResponse(PLArenaPool *arena, int error) +{ + CERTOCSPResponse response; + SECItem *result = NULL; + + switch (error) { + case SEC_ERROR_OCSP_MALFORMED_REQUEST: + response.statusValue = ocspResponse_malformedRequest; + break; + case SEC_ERROR_OCSP_SERVER_ERROR: + response.statusValue = ocspResponse_internalError; + break; + case SEC_ERROR_OCSP_TRY_SERVER_LATER: + response.statusValue = ocspResponse_tryLater; + break; + case SEC_ERROR_OCSP_REQUEST_NEEDS_SIG: + response.statusValue = ocspResponse_sigRequired; + break; + case SEC_ERROR_OCSP_UNAUTHORIZED_REQUEST: + response.statusValue = ocspResponse_unauthorized; + break; + default: + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + if (!SEC_ASN1EncodeInteger(NULL, &response.responseStatus, + response.statusValue)) + return NULL; + + result = SEC_ASN1EncodeItem(arena, NULL, &response, + ocsp_OCSPErrorResponseTemplate); + + SECITEM_FreeItem(&response.responseStatus, PR_FALSE); + + return result; +} |