michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=8 sts=2 et sw=2 tw=80: */ michael@0: /* Copyright 2013 Mozilla Foundation michael@0: * michael@0: * Licensed under the Apache License, Version 2.0 (the "License"); michael@0: * you may not use this file except in compliance with the License. michael@0: * You may obtain a copy of the License at michael@0: * michael@0: * http://www.apache.org/licenses/LICENSE-2.0 michael@0: * michael@0: * Unless required by applicable law or agreed to in writing, software michael@0: * distributed under the License is distributed on an "AS IS" BASIS, michael@0: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. michael@0: * See the License for the specific language governing permissions and michael@0: * limitations under the License. michael@0: */ michael@0: michael@0: #include "pkixcheck.h" michael@0: #include "pkixder.h" michael@0: #include "pkixtestutil.h" michael@0: michael@0: #include "cryptohi.h" michael@0: #include "hasht.h" michael@0: #include "pk11pub.h" michael@0: #include "prinit.h" michael@0: #include "secder.h" michael@0: michael@0: namespace mozilla { namespace pkix { namespace test { michael@0: michael@0: class Output michael@0: { michael@0: public: michael@0: Output() michael@0: : numItems(0) michael@0: , length(0) michael@0: { michael@0: } michael@0: michael@0: // Makes a shallow copy of the input item. All input items must have a michael@0: // lifetime that extends at least to where Squash is called. michael@0: der::Result Add(const SECItem* item) michael@0: { michael@0: PR_ASSERT(item); michael@0: PR_ASSERT(item->data); michael@0: michael@0: if (numItems >= MaxSequenceItems) { michael@0: return der::Fail(SEC_ERROR_INVALID_ARGS); michael@0: } michael@0: if (length + item->len > 65535) { michael@0: return der::Fail(SEC_ERROR_INVALID_ARGS); michael@0: } michael@0: michael@0: contents[numItems] = item; michael@0: numItems++; michael@0: length += item->len; michael@0: return der::Success; michael@0: } michael@0: michael@0: SECItem* Squash(PLArenaPool* arena, uint8_t tag) michael@0: { michael@0: PR_ASSERT(arena); michael@0: michael@0: size_t lengthLength = length < 128 ? 1 michael@0: : length < 256 ? 2 michael@0: : 3; michael@0: size_t totalLength = 1 + lengthLength + length; michael@0: SECItem* output = SECITEM_AllocItem(arena, nullptr, totalLength); michael@0: if (!output) { michael@0: return nullptr; michael@0: } michael@0: uint8_t* d = output->data; michael@0: *d++ = tag; michael@0: EncodeLength(d, length, lengthLength); michael@0: d += lengthLength; michael@0: for (size_t i = 0; i < numItems; i++) { michael@0: memcpy(d, contents[i]->data, contents[i]->len); michael@0: d += contents[i]->len; michael@0: } michael@0: return output; michael@0: } michael@0: michael@0: private: michael@0: void michael@0: EncodeLength(uint8_t* data, size_t length, size_t lengthLength) michael@0: { michael@0: switch (lengthLength) { michael@0: case 1: michael@0: data[0] = length; michael@0: break; michael@0: case 2: michael@0: data[0] = 0x81; michael@0: data[1] = length; michael@0: break; michael@0: case 3: michael@0: data[0] = 0x82; michael@0: data[1] = length / 256; michael@0: data[2] = length % 256; michael@0: break; michael@0: default: michael@0: PR_NOT_REACHED("EncodeLength: bad lengthLength"); michael@0: PR_Abort(); michael@0: } michael@0: } michael@0: michael@0: static const size_t MaxSequenceItems = 5; michael@0: const SECItem* contents[MaxSequenceItems]; michael@0: size_t numItems; michael@0: size_t length; michael@0: michael@0: Output(const Output&) /* = delete */; michael@0: void operator=(const Output&) /* = delete */; michael@0: }; michael@0: michael@0: OCSPResponseContext::OCSPResponseContext(PLArenaPool* arena, michael@0: CERTCertificate* cert, michael@0: PRTime time) michael@0: : arena(arena) michael@0: , cert(CERT_DupCertificate(cert)) michael@0: , issuerCert(nullptr) michael@0: , signerCert(nullptr) michael@0: , responseStatus(0) michael@0: , skipResponseBytes(false) michael@0: , producedAt(time) michael@0: , thisUpdate(time) michael@0: , nextUpdate(time + 10 * PR_USEC_PER_SEC) michael@0: , includeNextUpdate(true) michael@0: , certIDHashAlg(SEC_OID_SHA1) michael@0: , certStatus(0) michael@0: , revocationTime(0) michael@0: , badSignature(false) michael@0: , responderIDType(ByKeyHash) michael@0: , extensions(nullptr) michael@0: , includeEmptyExtensions(false) michael@0: { michael@0: for (size_t i = 0; i < MaxIncludedCertificates; i++) { michael@0: includedCertificates[i] = nullptr; michael@0: } michael@0: } michael@0: michael@0: static SECItem* ResponseBytes(OCSPResponseContext& context); michael@0: static SECItem* BasicOCSPResponse(OCSPResponseContext& context); michael@0: static SECItem* ResponseData(OCSPResponseContext& context); michael@0: static SECItem* ResponderID(OCSPResponseContext& context); michael@0: static SECItem* KeyHash(OCSPResponseContext& context); michael@0: static SECItem* SingleResponse(OCSPResponseContext& context); michael@0: static SECItem* CertID(OCSPResponseContext& context); michael@0: static SECItem* CertStatus(OCSPResponseContext& context); michael@0: static SECItem* Certificates(OCSPResponseContext& context); michael@0: michael@0: static SECItem* michael@0: EncodeNested(PLArenaPool* arena, uint8_t tag, SECItem* inner) michael@0: { michael@0: Output output; michael@0: if (output.Add(inner) != der::Success) { michael@0: return nullptr; michael@0: } michael@0: return output.Squash(arena, tag); michael@0: } michael@0: michael@0: // A return value of 0 is an error, but this should never happen in practice michael@0: // because this function aborts in that case. michael@0: static size_t michael@0: HashAlgorithmToLength(SECOidTag hashAlg) michael@0: { michael@0: switch (hashAlg) { michael@0: case SEC_OID_SHA1: michael@0: return SHA1_LENGTH; michael@0: case SEC_OID_SHA256: michael@0: return SHA256_LENGTH; michael@0: case SEC_OID_SHA384: michael@0: return SHA384_LENGTH; michael@0: case SEC_OID_SHA512: michael@0: return SHA512_LENGTH; michael@0: default: michael@0: PR_NOT_REACHED("HashAlgorithmToLength: bad hashAlg"); michael@0: PR_Abort(); michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: static SECItem* michael@0: HashedOctetString(PLArenaPool* arena, const SECItem* bytes, SECOidTag hashAlg) michael@0: { michael@0: size_t hashLen = HashAlgorithmToLength(hashAlg); michael@0: if (hashLen == 0) { michael@0: return nullptr; michael@0: } michael@0: SECItem* hashBuf = SECITEM_AllocItem(arena, nullptr, hashLen); michael@0: if (!hashBuf) { michael@0: return nullptr; michael@0: } michael@0: if (PK11_HashBuf(hashAlg, hashBuf->data, bytes->data, bytes->len) michael@0: != SECSuccess) { michael@0: return nullptr; michael@0: } michael@0: michael@0: return EncodeNested(arena, der::OCTET_STRING, hashBuf); michael@0: } michael@0: michael@0: static SECItem* michael@0: KeyHashHelper(PLArenaPool* arena, const CERTCertificate* cert) michael@0: { michael@0: // We only need a shallow copy here. michael@0: SECItem spk = cert->subjectPublicKeyInfo.subjectPublicKey; michael@0: DER_ConvertBitString(&spk); // bits to bytes michael@0: return HashedOctetString(arena, &spk, SEC_OID_SHA1); michael@0: } michael@0: michael@0: static SECItem* michael@0: AlgorithmIdentifier(PLArenaPool* arena, SECOidTag algTag) michael@0: { michael@0: SECAlgorithmIDStr aid; michael@0: aid.algorithm.data = nullptr; michael@0: aid.algorithm.len = 0; michael@0: aid.parameters.data = nullptr; michael@0: aid.parameters.len = 0; michael@0: if (SECOID_SetAlgorithmID(arena, &aid, algTag, nullptr) != SECSuccess) { michael@0: return nullptr; michael@0: } michael@0: static const SEC_ASN1Template algorithmIDTemplate[] = { michael@0: { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SECAlgorithmID) }, michael@0: { SEC_ASN1_OBJECT_ID, offsetof(SECAlgorithmID, algorithm) }, michael@0: { SEC_ASN1_OPTIONAL | SEC_ASN1_ANY, offsetof(SECAlgorithmID, parameters) }, michael@0: { 0 } michael@0: }; michael@0: SECItem* algorithmID = SEC_ASN1EncodeItem(arena, nullptr, &aid, michael@0: algorithmIDTemplate); michael@0: return algorithmID; michael@0: } michael@0: michael@0: static SECItem* michael@0: PRTimeToEncodedTime(PLArenaPool* arena, PRTime time) michael@0: { michael@0: SECItem derTime; michael@0: if (DER_TimeToGeneralizedTimeArena(arena, &derTime, time) != SECSuccess) { michael@0: return nullptr; michael@0: } michael@0: return EncodeNested(arena, der::GENERALIZED_TIME, &derTime); michael@0: } michael@0: michael@0: SECItem* michael@0: CreateEncodedOCSPResponse(OCSPResponseContext& context) michael@0: { michael@0: if (!context.arena || !context.cert || !context.issuerCert || michael@0: !context.signerCert) { michael@0: PR_SetError(SEC_ERROR_INVALID_ARGS, 0); michael@0: return nullptr; michael@0: } michael@0: michael@0: // OCSPResponse ::= SEQUENCE { michael@0: // responseStatus OCSPResponseStatus, michael@0: // responseBytes [0] EXPLICIT ResponseBytes OPTIONAL } michael@0: michael@0: // OCSPResponseStatus ::= ENUMERATED { michael@0: // successful (0), -- Response has valid confirmations michael@0: // malformedRequest (1), -- Illegal confirmation request michael@0: // internalError (2), -- Internal error in issuer michael@0: // tryLater (3), -- Try again later michael@0: // -- (4) is not used michael@0: // sigRequired (5), -- Must sign the request michael@0: // unauthorized (6) -- Request unauthorized michael@0: // } michael@0: SECItem* responseStatus = SECITEM_AllocItem(context.arena, nullptr, 3); michael@0: if (!responseStatus) { michael@0: return nullptr; michael@0: } michael@0: responseStatus->data[0] = der::ENUMERATED; michael@0: responseStatus->data[1] = 1; michael@0: responseStatus->data[2] = context.responseStatus; michael@0: michael@0: SECItem* responseBytesNested = nullptr; michael@0: if (!context.skipResponseBytes) { michael@0: SECItem* responseBytes = ResponseBytes(context); michael@0: if (!responseBytes) { michael@0: return nullptr; michael@0: } michael@0: michael@0: responseBytesNested = EncodeNested(context.arena, michael@0: der::CONSTRUCTED | michael@0: der::CONTEXT_SPECIFIC, michael@0: responseBytes); michael@0: if (!responseBytesNested) { michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: Output output; michael@0: if (output.Add(responseStatus) != der::Success) { michael@0: return nullptr; michael@0: } michael@0: if (responseBytesNested) { michael@0: if (output.Add(responseBytesNested) != der::Success) { michael@0: return nullptr; michael@0: } michael@0: } michael@0: return output.Squash(context.arena, der::SEQUENCE); michael@0: } michael@0: michael@0: // ResponseBytes ::= SEQUENCE { michael@0: // responseType OBJECT IDENTIFIER, michael@0: // response OCTET STRING } michael@0: SECItem* michael@0: ResponseBytes(OCSPResponseContext& context) michael@0: { michael@0: // Includes tag and length michael@0: static const uint8_t id_pkix_ocsp_basic_encoded[] = { michael@0: 0x06, 0x09, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x01 michael@0: }; michael@0: SECItem id_pkix_ocsp_basic = { michael@0: siBuffer, michael@0: const_cast(id_pkix_ocsp_basic_encoded), michael@0: PR_ARRAY_SIZE(id_pkix_ocsp_basic_encoded) michael@0: }; michael@0: SECItem* response = BasicOCSPResponse(context); michael@0: if (!response) { michael@0: return nullptr; michael@0: } michael@0: SECItem* responseNested = EncodeNested(context.arena, der::OCTET_STRING, michael@0: response); michael@0: if (!responseNested) { michael@0: return nullptr; michael@0: } michael@0: michael@0: Output output; michael@0: if (output.Add(&id_pkix_ocsp_basic) != der::Success) { michael@0: return nullptr; michael@0: } michael@0: if (output.Add(responseNested) != der::Success) { michael@0: return nullptr; michael@0: } michael@0: return output.Squash(context.arena, der::SEQUENCE); michael@0: } michael@0: michael@0: // BasicOCSPResponse ::= SEQUENCE { michael@0: // tbsResponseData ResponseData, michael@0: // signatureAlgorithm AlgorithmIdentifier, michael@0: // signature BIT STRING, michael@0: // certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL } michael@0: SECItem* michael@0: BasicOCSPResponse(OCSPResponseContext& context) michael@0: { michael@0: SECItem* tbsResponseData = ResponseData(context); michael@0: if (!tbsResponseData) { michael@0: return nullptr; michael@0: } michael@0: michael@0: michael@0: pkix::ScopedPtr privKey( michael@0: PK11_FindKeyByAnyCert(context.signerCert.get(), nullptr)); michael@0: if (!privKey) { michael@0: return nullptr; michael@0: } michael@0: SECOidTag signatureAlgTag = SEC_GetSignatureAlgorithmOidTag(privKey->keyType, michael@0: SEC_OID_SHA1); michael@0: if (signatureAlgTag == SEC_OID_UNKNOWN) { michael@0: return nullptr; michael@0: } michael@0: SECItem* signatureAlgorithm = AlgorithmIdentifier(context.arena, michael@0: signatureAlgTag); michael@0: if (!signatureAlgorithm) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // SEC_SignData doesn't take an arena parameter, so we have to manage michael@0: // the memory allocated in signature. michael@0: SECItem signature; michael@0: if (SEC_SignData(&signature, tbsResponseData->data, tbsResponseData->len, michael@0: privKey.get(), signatureAlgTag) != SECSuccess) michael@0: { michael@0: return nullptr; michael@0: } michael@0: // We have to add a byte at the beginning indicating no unused bits. michael@0: // TODO: add ability to have signatures of bit length not divisible by 8, michael@0: // resulting in unused bits in the bitstring encoding michael@0: SECItem* prefixedSignature = SECITEM_AllocItem(context.arena, nullptr, michael@0: signature.len + 1); michael@0: if (!prefixedSignature) { michael@0: SECITEM_FreeItem(&signature, false); michael@0: return nullptr; michael@0: } michael@0: prefixedSignature->data[0] = 0; michael@0: memcpy(prefixedSignature->data + 1, signature.data, signature.len); michael@0: SECITEM_FreeItem(&signature, false); michael@0: if (context.badSignature) { michael@0: PR_ASSERT(prefixedSignature->len > 8); michael@0: prefixedSignature->data[8]++; michael@0: } michael@0: SECItem* signatureNested = EncodeNested(context.arena, der::BIT_STRING, michael@0: prefixedSignature); michael@0: if (!signatureNested) { michael@0: return nullptr; michael@0: } michael@0: SECItem* certificatesNested = nullptr; michael@0: if (context.includedCertificates[0]) { michael@0: SECItem* certificates = Certificates(context); michael@0: if (!certificates) { michael@0: return nullptr; michael@0: } michael@0: certificatesNested = EncodeNested(context.arena, michael@0: der::CONSTRUCTED | michael@0: der::CONTEXT_SPECIFIC | michael@0: 0, michael@0: certificates); michael@0: if (!certificatesNested) { michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: Output output; michael@0: if (output.Add(tbsResponseData) != der::Success) { michael@0: return nullptr; michael@0: } michael@0: if (output.Add(signatureAlgorithm) != der::Success) { michael@0: return nullptr; michael@0: } michael@0: if (output.Add(signatureNested) != der::Success) { michael@0: return nullptr; michael@0: } michael@0: if (certificatesNested) { michael@0: if (output.Add(certificatesNested) != der::Success) { michael@0: return nullptr; michael@0: } michael@0: } michael@0: return output.Squash(context.arena, der::SEQUENCE); michael@0: } michael@0: michael@0: // Extension ::= SEQUENCE { michael@0: // id OBJECT IDENTIFIER, michael@0: // critical BOOLEAN DEFAULT FALSE michael@0: // value OCTET STRING michael@0: // } michael@0: static SECItem* michael@0: OCSPExtension(OCSPResponseContext& context, OCSPResponseExtension* extension) michael@0: { michael@0: Output output; michael@0: if (output.Add(&extension->id) != der::Success) { michael@0: return nullptr; michael@0: } michael@0: if (extension->critical) { michael@0: static const uint8_t trueEncoded[3] = { 0x01, 0x01, 0xFF }; michael@0: SECItem critical = { michael@0: siBuffer, michael@0: const_cast(trueEncoded), michael@0: PR_ARRAY_SIZE(trueEncoded) michael@0: }; michael@0: if (output.Add(&critical) != der::Success) { michael@0: return nullptr; michael@0: } michael@0: } michael@0: SECItem* value = EncodeNested(context.arena, der::OCTET_STRING, michael@0: &extension->value); michael@0: if (!value) { michael@0: return nullptr; michael@0: } michael@0: if (output.Add(value) != der::Success) { michael@0: return nullptr; michael@0: } michael@0: return output.Squash(context.arena, der::SEQUENCE); michael@0: } michael@0: michael@0: // Extensions ::= [1] { michael@0: // SEQUENCE OF Extension michael@0: // } michael@0: static SECItem* michael@0: Extensions(OCSPResponseContext& context) michael@0: { michael@0: Output output; michael@0: for (OCSPResponseExtension* extension = context.extensions; michael@0: extension; extension = extension->next) { michael@0: SECItem* extensionEncoded = OCSPExtension(context, extension); michael@0: if (!extensionEncoded) { michael@0: return nullptr; michael@0: } michael@0: if (output.Add(extensionEncoded) != der::Success) { michael@0: return nullptr; michael@0: } michael@0: } michael@0: SECItem* extensionsEncoded = output.Squash(context.arena, der::SEQUENCE); michael@0: if (!extensionsEncoded) { michael@0: return nullptr; michael@0: } michael@0: return EncodeNested(context.arena, michael@0: der::CONSTRUCTED | michael@0: der::CONTEXT_SPECIFIC | michael@0: 1, michael@0: extensionsEncoded); michael@0: } michael@0: michael@0: // ResponseData ::= SEQUENCE { michael@0: // version [0] EXPLICIT Version DEFAULT v1, michael@0: // responderID ResponderID, michael@0: // producedAt GeneralizedTime, michael@0: // responses SEQUENCE OF SingleResponse, michael@0: // responseExtensions [1] EXPLICIT Extensions OPTIONAL } michael@0: SECItem* michael@0: ResponseData(OCSPResponseContext& context) michael@0: { michael@0: SECItem* responderID = ResponderID(context); michael@0: if (!responderID) { michael@0: return nullptr; michael@0: } michael@0: SECItem* producedAtEncoded = PRTimeToEncodedTime(context.arena, michael@0: context.producedAt); michael@0: if (!producedAtEncoded) { michael@0: return nullptr; michael@0: } michael@0: SECItem* responses = SingleResponse(context); michael@0: if (!responses) { michael@0: return nullptr; michael@0: } michael@0: SECItem* responsesNested = EncodeNested(context.arena, der::SEQUENCE, michael@0: responses); michael@0: if (!responsesNested) { michael@0: return nullptr; michael@0: } michael@0: SECItem* responseExtensions = nullptr; michael@0: if (context.extensions || context.includeEmptyExtensions) { michael@0: responseExtensions = Extensions(context); michael@0: } michael@0: michael@0: Output output; michael@0: if (output.Add(responderID) != der::Success) { michael@0: return nullptr; michael@0: } michael@0: if (output.Add(producedAtEncoded) != der::Success) { michael@0: return nullptr; michael@0: } michael@0: if (output.Add(responsesNested) != der::Success) { michael@0: return nullptr; michael@0: } michael@0: if (responseExtensions) { michael@0: if (output.Add(responseExtensions) != der::Success) { michael@0: return nullptr; michael@0: } michael@0: } michael@0: return output.Squash(context.arena, der::SEQUENCE); michael@0: } michael@0: michael@0: // ResponderID ::= CHOICE { michael@0: // byName [1] Name, michael@0: // byKey [2] KeyHash } michael@0: // } michael@0: SECItem* michael@0: ResponderID(OCSPResponseContext& context) michael@0: { michael@0: SECItem* contents = nullptr; michael@0: if (context.responderIDType == OCSPResponseContext::ByName) { michael@0: contents = &context.signerCert->derSubject; michael@0: } else if (context.responderIDType == OCSPResponseContext::ByKeyHash) { michael@0: contents = KeyHash(context); michael@0: if (!contents) { michael@0: return nullptr; michael@0: } michael@0: } else { michael@0: return nullptr; michael@0: } michael@0: michael@0: return EncodeNested(context.arena, michael@0: der::CONSTRUCTED | michael@0: der::CONTEXT_SPECIFIC | michael@0: context.responderIDType, michael@0: contents); michael@0: } michael@0: michael@0: // KeyHash ::= OCTET STRING -- SHA-1 hash of responder's public key michael@0: // -- (i.e., the SHA-1 hash of the value of the michael@0: // -- BIT STRING subjectPublicKey [excluding michael@0: // -- the tag, length, and number of unused michael@0: // -- bits] in the responder's certificate) michael@0: SECItem* michael@0: KeyHash(OCSPResponseContext& context) michael@0: { michael@0: return KeyHashHelper(context.arena, context.signerCert.get()); michael@0: } michael@0: michael@0: // SingleResponse ::= SEQUENCE { michael@0: // certID CertID, michael@0: // certStatus CertStatus, michael@0: // thisUpdate GeneralizedTime, michael@0: // nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL, michael@0: // singleExtensions [1] EXPLICIT Extensions OPTIONAL } michael@0: SECItem* michael@0: SingleResponse(OCSPResponseContext& context) michael@0: { michael@0: SECItem* certID = CertID(context); michael@0: if (!certID) { michael@0: return nullptr; michael@0: } michael@0: SECItem* certStatus = CertStatus(context); michael@0: if (!certStatus) { michael@0: return nullptr; michael@0: } michael@0: SECItem* thisUpdateEncoded = PRTimeToEncodedTime(context.arena, michael@0: context.thisUpdate); michael@0: if (!thisUpdateEncoded) { michael@0: return nullptr; michael@0: } michael@0: SECItem* nextUpdateEncodedNested = nullptr; michael@0: if (context.includeNextUpdate) { michael@0: SECItem* nextUpdateEncoded = PRTimeToEncodedTime(context.arena, michael@0: context.nextUpdate); michael@0: if (!nextUpdateEncoded) { michael@0: return nullptr; michael@0: } michael@0: nextUpdateEncodedNested = EncodeNested(context.arena, michael@0: der::CONSTRUCTED | michael@0: der::CONTEXT_SPECIFIC | michael@0: 0, michael@0: nextUpdateEncoded); michael@0: if (!nextUpdateEncodedNested) { michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: Output output; michael@0: if (output.Add(certID) != der::Success) { michael@0: return nullptr; michael@0: } michael@0: if (output.Add(certStatus) != der::Success) { michael@0: return nullptr; michael@0: } michael@0: if (output.Add(thisUpdateEncoded) != der::Success) { michael@0: return nullptr; michael@0: } michael@0: if (nextUpdateEncodedNested) { michael@0: if (output.Add(nextUpdateEncodedNested) != der::Success) { michael@0: return nullptr; michael@0: } michael@0: } michael@0: return output.Squash(context.arena, der::SEQUENCE); michael@0: } michael@0: michael@0: // CertID ::= SEQUENCE { michael@0: // hashAlgorithm AlgorithmIdentifier, michael@0: // issuerNameHash OCTET STRING, -- Hash of issuer's DN michael@0: // issuerKeyHash OCTET STRING, -- Hash of issuer's public key michael@0: // serialNumber CertificateSerialNumber } michael@0: SECItem* michael@0: CertID(OCSPResponseContext& context) michael@0: { michael@0: SECItem* hashAlgorithm = AlgorithmIdentifier(context.arena, michael@0: context.certIDHashAlg); michael@0: if (!hashAlgorithm) { michael@0: return nullptr; michael@0: } michael@0: SECItem* issuerNameHash = HashedOctetString(context.arena, michael@0: &context.issuerCert->derSubject, michael@0: context.certIDHashAlg); michael@0: if (!issuerNameHash) { michael@0: return nullptr; michael@0: } michael@0: SECItem* issuerKeyHash = KeyHashHelper(context.arena, michael@0: context.issuerCert.get()); michael@0: if (!issuerKeyHash) { michael@0: return nullptr; michael@0: } michael@0: static const SEC_ASN1Template serialTemplate[] = { michael@0: { SEC_ASN1_INTEGER, offsetof(CERTCertificate, serialNumber) }, michael@0: { 0 } michael@0: }; michael@0: SECItem* serialNumber = SEC_ASN1EncodeItem(context.arena, nullptr, michael@0: context.cert.get(), michael@0: serialTemplate); michael@0: if (!serialNumber) { michael@0: return nullptr; michael@0: } michael@0: michael@0: Output output; michael@0: if (output.Add(hashAlgorithm) != der::Success) { michael@0: return nullptr; michael@0: } michael@0: if (output.Add(issuerNameHash) != der::Success) { michael@0: return nullptr; michael@0: } michael@0: if (output.Add(issuerKeyHash) != der::Success) { michael@0: return nullptr; michael@0: } michael@0: if (output.Add(serialNumber) != der::Success) { michael@0: return nullptr; michael@0: } michael@0: return output.Squash(context.arena, der::SEQUENCE); michael@0: } michael@0: michael@0: // CertStatus ::= CHOICE { michael@0: // good [0] IMPLICIT NULL, michael@0: // revoked [1] IMPLICIT RevokedInfo, michael@0: // unknown [2] IMPLICIT UnknownInfo } michael@0: // michael@0: // RevokedInfo ::= SEQUENCE { michael@0: // revocationTime GeneralizedTime, michael@0: // revocationReason [0] EXPLICIT CRLReason OPTIONAL } michael@0: // michael@0: // UnknownInfo ::= NULL michael@0: // michael@0: SECItem* michael@0: CertStatus(OCSPResponseContext& context) michael@0: { michael@0: switch (context.certStatus) { michael@0: // Both good and unknown are ultimately represented as NULL - the only michael@0: // difference is in the tag that identifies them. michael@0: case 0: michael@0: case 2: michael@0: { michael@0: SECItem* status = SECITEM_AllocItem(context.arena, nullptr, 2); michael@0: if (!status) { michael@0: return nullptr; michael@0: } michael@0: status->data[0] = der::CONTEXT_SPECIFIC | context.certStatus; michael@0: status->data[1] = 0; michael@0: return status; michael@0: } michael@0: case 1: michael@0: { michael@0: SECItem* revocationTime = PRTimeToEncodedTime(context.arena, michael@0: context.revocationTime); michael@0: if (!revocationTime) { michael@0: return nullptr; michael@0: } michael@0: // TODO(bug 980536): add support for revocationReason michael@0: return EncodeNested(context.arena, michael@0: der::CONSTRUCTED | der::CONTEXT_SPECIFIC | 1, michael@0: revocationTime); michael@0: } michael@0: default: michael@0: PR_NOT_REACHED("CertStatus: bad context.certStatus"); michael@0: PR_Abort(); michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: // SEQUENCE OF Certificate michael@0: SECItem* michael@0: Certificates(OCSPResponseContext& context) michael@0: { michael@0: Output output; michael@0: for (size_t i = 0; i < context.MaxIncludedCertificates; i++) { michael@0: CERTCertificate* cert = context.includedCertificates[i].get(); michael@0: if (!cert) { michael@0: break; michael@0: } michael@0: output.Add(&cert->derCert); michael@0: } michael@0: return output.Squash(context.arena, der::SEQUENCE); michael@0: } michael@0: michael@0: } } } // namespace mozilla::pkix::test