Source code

Revision control

Copy as Markdown

Other Tools

/* 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;
}