gecko/security/pkix/test/lib/pkixtestutil.cpp

1079 lines
32 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This code is made available to you under your choice of the following sets
* of licensing terms:
*/
/* 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/.
*/
/* Copyright 2013 Mozilla Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "pkixtestutil.h"
#include <cerrno>
#include <cstdio>
#include <limits>
#include <new>
#include <sstream>
#include "pkixder.h"
#include "pkixutil.h"
using namespace std;
namespace mozilla { namespace pkix { namespace test {
// python DottedOIDToCode.py --alg sha256WithRSAEncryption 1.2.840.113549.1.1.11
static const uint8_t alg_sha256WithRSAEncryption[] = {
0x30, 0x0b, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b
};
const Input sha256WithRSAEncryption(alg_sha256WithRSAEncryption);
namespace {
inline void
fclose_void(FILE* file) {
(void) fclose(file);
}
typedef mozilla::pkix::ScopedPtr<FILE, fclose_void> ScopedFILE;
FILE*
OpenFile(const string& dir, const string& filename, const string& mode)
{
string path = dir + '/' + filename;
ScopedFILE file;
#ifdef _MSC_VER
{
FILE* rawFile;
errno_t error = fopen_s(&rawFile, path.c_str(), mode.c_str());
if (error) {
// TODO: map error to NSPR error code
rawFile = nullptr;
}
file = rawFile;
}
#else
file = fopen(path.c_str(), mode.c_str());
#endif
return file.release();
}
} // unnamed namespace
Result
TamperOnce(/*in/out*/ ByteString& item, const ByteString& from,
const ByteString& to)
{
if (from.length() < 8) {
return Result::FATAL_ERROR_INVALID_ARGS;
}
if (from.length() != to.length()) {
return Result::FATAL_ERROR_INVALID_ARGS;
}
size_t pos = item.find(from);
if (pos == string::npos) {
return Result::FATAL_ERROR_INVALID_ARGS; // No matches.
}
if (item.find(from, pos + from.length()) != string::npos) {
return Result::FATAL_ERROR_INVALID_ARGS; // More than once match.
}
item.replace(pos, from.length(), to);
return Success;
}
// An empty string returned from an encoding function signifies failure.
const ByteString ENCODING_FAILED;
// Given a tag and a value, generates a DER-encoded tag-length-value item.
static ByteString
TLV(uint8_t tag, const ByteString& value)
{
ByteString result;
result.push_back(tag);
if (value.length() < 128) {
result.push_back(value.length());
} else if (value.length() < 256) {
result.push_back(0x81u);
result.push_back(value.length());
} else if (value.length() < 65536) {
result.push_back(0x82u);
result.push_back(static_cast<uint8_t>(value.length() / 256));
result.push_back(static_cast<uint8_t>(value.length() % 256));
} else {
assert(false);
return ENCODING_FAILED;
}
result.append(value);
return result;
}
OCSPResponseContext::OCSPResponseContext(const CertID& certID, time_t time)
: certID(certID)
, responseStatus(successful)
, skipResponseBytes(false)
, producedAt(time)
, extensions(nullptr)
, includeEmptyExtensions(false)
, badSignature(false)
, certs(nullptr)
, certStatus(good)
, revocationTime(0)
, thisUpdate(time)
, nextUpdate(time + 10)
, includeNextUpdate(true)
{
}
static ByteString ResponseBytes(OCSPResponseContext& context);
static ByteString BasicOCSPResponse(OCSPResponseContext& context);
static ByteString ResponseData(OCSPResponseContext& context);
static ByteString ResponderID(OCSPResponseContext& context);
static ByteString KeyHash(const ByteString& subjectPublicKeyInfo);
static ByteString SingleResponse(OCSPResponseContext& context);
static ByteString CertID(OCSPResponseContext& context);
static ByteString CertStatus(OCSPResponseContext& context);
static ByteString
HashedOctetString(const ByteString& bytes)
{
ByteString digest(SHA1(bytes));
if (digest == ENCODING_FAILED) {
return ENCODING_FAILED;
}
return TLV(der::OCTET_STRING, digest);
}
static ByteString
BitString(const ByteString& rawBytes, bool corrupt)
{
ByteString prefixed;
// We have to add a byte at the beginning indicating no unused bits.
// TODO: add ability to have bit strings of bit length not divisible by 8,
// resulting in unused bits in the bitstring encoding
prefixed.push_back(0);
prefixed.append(rawBytes);
if (corrupt) {
assert(prefixed.length() > 8);
prefixed[8]++;
}
return TLV(der::BIT_STRING, prefixed);
}
static ByteString
Boolean(bool value)
{
ByteString encodedValue;
encodedValue.push_back(value ? 0xff : 0x00);
return TLV(der::BOOLEAN, encodedValue);
}
static ByteString
Integer(long value)
{
if (value < 0 || value > 127) {
// TODO: add encoding of larger values
return ENCODING_FAILED;
}
ByteString encodedValue;
encodedValue.push_back(static_cast<uint8_t>(value));
return TLV(der::INTEGER, encodedValue);
}
enum TimeEncoding { UTCTime = 0, GeneralizedTime = 1 };
// Windows doesn't provide gmtime_r, but it provides something very similar.
#ifdef WIN32
static tm*
gmtime_r(const time_t* t, /*out*/ tm* exploded)
{
if (gmtime_s(exploded, t) != 0) {
return nullptr;
}
return exploded;
}
#endif
// http://tools.ietf.org/html/rfc5280#section-4.1.2.5
// UTCTime: YYMMDDHHMMSSZ (years 1950-2049 only)
// GeneralizedTime: YYYYMMDDHHMMSSZ
//
// This assumes that time/time_t are POSIX-compliant in that time() returns
// the number of seconds since the Unix epoch.
static ByteString
TimeToEncodedTime(time_t time, TimeEncoding encoding)
{
assert(encoding == UTCTime || encoding == GeneralizedTime);
tm exploded;
if (!gmtime_r(&time, &exploded)) {
return ENCODING_FAILED;
}
if (exploded.tm_sec >= 60) {
// round down for leap seconds
exploded.tm_sec = 59;
}
// exploded.tm_year is the year offset by 1900.
int year = exploded.tm_year + 1900;
if (encoding == UTCTime && (year < 1950 || year >= 2050)) {
return ENCODING_FAILED;
}
ByteString value;
if (encoding == GeneralizedTime) {
value.push_back('0' + (year / 1000));
value.push_back('0' + ((year % 1000) / 100));
}
value.push_back('0' + ((year % 100) / 10));
value.push_back('0' + (year % 10));
value.push_back('0' + ((exploded.tm_mon + 1) / 10));
value.push_back('0' + ((exploded.tm_mon + 1) % 10));
value.push_back('0' + (exploded.tm_mday / 10));
value.push_back('0' + (exploded.tm_mday % 10));
value.push_back('0' + (exploded.tm_hour / 10));
value.push_back('0' + (exploded.tm_hour % 10));
value.push_back('0' + (exploded.tm_min / 10));
value.push_back('0' + (exploded.tm_min % 10));
value.push_back('0' + (exploded.tm_sec / 10));
value.push_back('0' + (exploded.tm_sec % 10));
value.push_back('Z');
return TLV(encoding == GeneralizedTime ? der::GENERALIZED_TIME : der::UTCTime,
value);
}
static ByteString
TimeToGeneralizedTime(time_t time)
{
return TimeToEncodedTime(time, GeneralizedTime);
}
// http://tools.ietf.org/html/rfc5280#section-4.1.2.5: "CAs conforming to this
// profile MUST always encode certificate validity dates through the year 2049
// as UTCTime; certificate validity dates in 2050 or later MUST be encoded as
// GeneralizedTime." (This is a special case of the rule that we must always
// use the shortest possible encoding.)
static ByteString
TimeToTimeChoice(time_t time)
{
tm exploded;
if (!gmtime_r(&time, &exploded)) {
return ENCODING_FAILED;
}
TimeEncoding encoding = (exploded.tm_year + 1900 >= 1950 &&
exploded.tm_year + 1900 < 2050)
? UTCTime
: GeneralizedTime;
return TimeToEncodedTime(time, encoding);
}
Time
YMDHMS(int16_t year, int16_t month, int16_t day,
int16_t hour, int16_t minutes, int16_t seconds)
{
assert(year <= 9999);
assert(month >= 1);
assert(month <= 12);
assert(day >= 1);
assert(hour >= 0);
assert(hour < 24);
assert(minutes >= 0);
assert(minutes < 60);
assert(seconds >= 0);
assert(seconds < 60);
uint64_t days = DaysBeforeYear(year);
{
static const int16_t DAYS_IN_MONTH[] = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
int16_t i = 1;
for (;;) {
int16_t daysInMonth = DAYS_IN_MONTH[i - 1];
if (i == 2 &&
((year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)))) {
// Add leap day
++daysInMonth;
}
if (i == month) {
assert(day <= daysInMonth);
break;
}
days += daysInMonth;
++i;
}
}
days += (day - 1);
uint64_t totalSeconds = days * Time::ONE_DAY_IN_SECONDS;
totalSeconds += hour * 60 * 60;
totalSeconds += minutes * 60;
totalSeconds += seconds;
return TimeFromElapsedSecondsAD(totalSeconds);
}
static ByteString
SignedData(const ByteString& tbsData,
TestKeyPair& keyPair,
SignatureAlgorithm signatureAlgorithm,
bool corrupt, /*optional*/ const ByteString* certs)
{
ByteString signature;
if (keyPair.SignData(tbsData, signatureAlgorithm, signature) != Success) {
return ENCODING_FAILED;
}
ByteString signatureAlgorithmDER;
switch (signatureAlgorithm) {
case SignatureAlgorithm::rsa_pkcs1_with_sha256:
signatureAlgorithmDER.assign(alg_sha256WithRSAEncryption,
sizeof(alg_sha256WithRSAEncryption));
break;
default:
return ENCODING_FAILED;
}
// TODO: add ability to have signatures of bit length not divisible by 8,
// resulting in unused bits in the bitstring encoding
ByteString signatureNested(BitString(signature, corrupt));
if (signatureNested == ENCODING_FAILED) {
return ENCODING_FAILED;
}
ByteString certsNested;
if (certs) {
ByteString certsSequenceValue;
while (!(*certs).empty()) {
certsSequenceValue.append(*certs);
++certs;
}
ByteString certsSequence(TLV(der::SEQUENCE, certsSequenceValue));
if (certsSequence == ENCODING_FAILED) {
return ENCODING_FAILED;
}
certsNested = TLV(der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0,
certsSequence);
if (certsNested == ENCODING_FAILED) {
return ENCODING_FAILED;
}
}
ByteString value;
value.append(tbsData);
value.append(signatureAlgorithmDER);
value.append(signatureNested);
value.append(certsNested);
return TLV(der::SEQUENCE, value);
}
// Extension ::= SEQUENCE {
// extnID OBJECT IDENTIFIER,
// critical BOOLEAN DEFAULT FALSE,
// extnValue OCTET STRING
// -- contains the DER encoding of an ASN.1 value
// -- corresponding to the extension type identified
// -- by extnID
// }
static ByteString
Extension(Input extnID, ExtensionCriticality criticality,
const ByteString& extnValueBytes)
{
ByteString encoded;
encoded.append(ByteString(extnID.UnsafeGetData(), extnID.GetLength()));
if (criticality == ExtensionCriticality::Critical) {
ByteString critical(Boolean(true));
if (critical == ENCODING_FAILED) {
return ENCODING_FAILED;
}
encoded.append(critical);
}
ByteString extnValueSequence(TLV(der::SEQUENCE, extnValueBytes));
if (extnValueBytes == ENCODING_FAILED) {
return ENCODING_FAILED;
}
ByteString extnValue(TLV(der::OCTET_STRING, extnValueSequence));
if (extnValue == ENCODING_FAILED) {
return ENCODING_FAILED;
}
encoded.append(extnValue);
return TLV(der::SEQUENCE, encoded);
}
void
MaybeLogOutput(const ByteString& result, const char* suffix)
{
assert(suffix);
// This allows us to more easily debug the generated output, by creating a
// file in the directory given by MOZILLA_PKIX_TEST_LOG_DIR for each
// NOT THREAD-SAFE!!!
const char* logPath = getenv("MOZILLA_PKIX_TEST_LOG_DIR");
if (logPath) {
static int counter = 0;
std::ostringstream counterStream;
counterStream << counter;
if (!counterStream) {
assert(false);
return;
}
string filename = counterStream.str() + '-' + suffix + ".der";
++counter;
ScopedFILE file(OpenFile(logPath, filename, "wb"));
if (file) {
(void) fwrite(result.data(), result.length(), 1, file.get());
}
}
}
///////////////////////////////////////////////////////////////////////////////
// Certificates
static ByteString TBSCertificate(long version, const ByteString& serialNumber,
Input signature, const ByteString& issuer,
time_t notBefore, time_t notAfter,
const ByteString& subject,
const ByteString& subjectPublicKeyInfo,
/*optional*/ const ByteString* extensions);
// Certificate ::= SEQUENCE {
// tbsCertificate TBSCertificate,
// signatureAlgorithm AlgorithmIdentifier,
// signatureValue BIT STRING }
ByteString
CreateEncodedCertificate(long version, Input signature,
const ByteString& serialNumber,
const ByteString& issuerNameDER,
time_t notBefore, time_t notAfter,
const ByteString& subjectNameDER,
/*optional*/ const ByteString* extensions,
/*optional*/ TestKeyPair* issuerKeyPair,
SignatureAlgorithm signatureAlgorithm,
/*out*/ ScopedTestKeyPair& keyPairResult)
{
// It may be the case that privateKeyResult references the same TestKeyPair
// as issuerKeyPair. Thus, we can't set keyPairResult until after we're done
// with issuerKeyPair.
ScopedTestKeyPair subjectKeyPair(GenerateKeyPair());
if (!subjectKeyPair) {
return ENCODING_FAILED;
}
ByteString tbsCertificate(TBSCertificate(version, serialNumber,
signature, issuerNameDER, notBefore,
notAfter, subjectNameDER,
subjectKeyPair->subjectPublicKeyInfo,
extensions));
if (tbsCertificate == ENCODING_FAILED) {
return ENCODING_FAILED;
}
ByteString result(SignedData(tbsCertificate,
issuerKeyPair ? *issuerKeyPair
: *subjectKeyPair,
signatureAlgorithm, false, nullptr));
if (result == ENCODING_FAILED) {
return ENCODING_FAILED;
}
MaybeLogOutput(result, "cert");
keyPairResult = subjectKeyPair.release();
return result;
}
// TBSCertificate ::= SEQUENCE {
// version [0] Version DEFAULT v1,
// serialNumber CertificateSerialNumber,
// signature AlgorithmIdentifier,
// issuer Name,
// validity Validity,
// subject Name,
// subjectPublicKeyInfo SubjectPublicKeyInfo,
// issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
// -- If present, version MUST be v2 or v3
// subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
// -- If present, version MUST be v2 or v3
// extensions [3] Extensions OPTIONAL
// -- If present, version MUST be v3 -- }
static ByteString
TBSCertificate(long versionValue,
const ByteString& serialNumber, Input signature,
const ByteString& issuer, time_t notBeforeTime,
time_t notAfterTime, const ByteString& subject,
const ByteString& subjectPublicKeyInfo,
/*optional*/ const ByteString* extensions)
{
ByteString value;
if (versionValue != static_cast<long>(der::Version::v1)) {
ByteString versionInteger(Integer(versionValue));
if (versionInteger == ENCODING_FAILED) {
return ENCODING_FAILED;
}
ByteString version(TLV(der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0,
versionInteger));
if (version == ENCODING_FAILED) {
return ENCODING_FAILED;
}
value.append(version);
}
value.append(serialNumber);
value.append(signature.UnsafeGetData(), signature.GetLength());
value.append(issuer);
// Validity ::= SEQUENCE {
// notBefore Time,
// notAfter Time }
ByteString validity;
{
ByteString notBefore(TimeToTimeChoice(notBeforeTime));
if (notBefore == ENCODING_FAILED) {
return ENCODING_FAILED;
}
ByteString notAfter(TimeToTimeChoice(notAfterTime));
if (notAfter == ENCODING_FAILED) {
return ENCODING_FAILED;
}
ByteString validityValue;
validityValue.append(notBefore);
validityValue.append(notAfter);
validity = TLV(der::SEQUENCE, validityValue);
if (validity == ENCODING_FAILED) {
return ENCODING_FAILED;
}
}
value.append(validity);
value.append(subject);
value.append(subjectPublicKeyInfo);
if (extensions) {
ByteString extensionsValue;
while (!(*extensions).empty()) {
extensionsValue.append(*extensions);
++extensions;
}
ByteString extensionsSequence(TLV(der::SEQUENCE, extensionsValue));
if (extensionsSequence == ENCODING_FAILED) {
return ENCODING_FAILED;
}
ByteString extensionsWrapped(
TLV(der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 3, extensionsSequence));
if (extensionsWrapped == ENCODING_FAILED) {
return ENCODING_FAILED;
}
value.append(extensionsWrapped);
}
return TLV(der::SEQUENCE, value);
}
ByteString
CNToDERName(const char* cn)
{
// Name ::= CHOICE { -- only one possibility for now --
// rdnSequence RDNSequence }
//
// RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
//
// RelativeDistinguishedName ::=
// SET SIZE (1..MAX) OF AttributeTypeAndValue
//
// AttributeTypeAndValue ::= SEQUENCE {
// type AttributeType,
// value AttributeValue }
//
// AttributeType ::= OBJECT IDENTIFIER
//
// AttributeValue ::= ANY -- DEFINED BY AttributeType
//
// DirectoryString ::= CHOICE {
// teletexString TeletexString (SIZE (1..MAX)),
// printableString PrintableString (SIZE (1..MAX)),
// universalString UniversalString (SIZE (1..MAX)),
// utf8String UTF8String (SIZE (1..MAX)),
// bmpString BMPString (SIZE (1..MAX)) }
//
// id-at OBJECT IDENTIFIER ::= { joint-iso-ccitt(2) ds(5) 4 }
// id-at-commonName AttributeType ::= { id-at 3 }
// python DottedOIDToCode.py --tlv id-at-commonName 2.5.4.3
static const uint8_t tlv_id_at_commonName[] = {
0x06, 0x03, 0x55, 0x04, 0x03
};
ByteString value(reinterpret_cast<const ByteString::value_type*>(cn));
value = TLV(der::UTF8String, value);
ByteString ava;
ava.append(tlv_id_at_commonName, sizeof(tlv_id_at_commonName));
ava.append(value);
ava = TLV(der::SEQUENCE, ava);
if (ava == ENCODING_FAILED) {
return ENCODING_FAILED;
}
ByteString rdn(TLV(der::SET, ava));
if (rdn == ENCODING_FAILED) {
return ENCODING_FAILED;
}
return TLV(der::SEQUENCE, rdn);
}
ByteString
CreateEncodedSerialNumber(long serialNumberValue)
{
return Integer(serialNumberValue);
}
// BasicConstraints ::= SEQUENCE {
// cA BOOLEAN DEFAULT FALSE,
// pathLenConstraint INTEGER (0..MAX) OPTIONAL }
ByteString
CreateEncodedBasicConstraints(bool isCA,
/*optional*/ long* pathLenConstraintValue,
ExtensionCriticality criticality)
{
ByteString value;
if (isCA) {
ByteString cA(Boolean(true));
if (cA == ENCODING_FAILED) {
return ENCODING_FAILED;
}
value.append(cA);
}
if (pathLenConstraintValue) {
ByteString pathLenConstraint(Integer(*pathLenConstraintValue));
if (pathLenConstraint == ENCODING_FAILED) {
return ENCODING_FAILED;
}
value.append(pathLenConstraint);
}
// python DottedOIDToCode.py --tlv id-ce-basicConstraints 2.5.29.19
static const uint8_t tlv_id_ce_basicConstraints[] = {
0x06, 0x03, 0x55, 0x1d, 0x13
};
return Extension(Input(tlv_id_ce_basicConstraints), criticality, value);
}
// ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId
// KeyPurposeId ::= OBJECT IDENTIFIER
ByteString
CreateEncodedEKUExtension(Input ekuOID, ExtensionCriticality criticality)
{
ByteString value(ekuOID.UnsafeGetData(), ekuOID.GetLength());
// python DottedOIDToCode.py --tlv id-ce-extKeyUsage 2.5.29.37
static const uint8_t tlv_id_ce_extKeyUsage[] = {
0x06, 0x03, 0x55, 0x1d, 0x25
};
return Extension(Input(tlv_id_ce_extKeyUsage), criticality, value);
}
///////////////////////////////////////////////////////////////////////////////
// OCSP responses
ByteString
CreateEncodedOCSPResponse(OCSPResponseContext& context)
{
if (!context.skipResponseBytes) {
if (!context.signerKeyPair) {
return ENCODING_FAILED;
}
}
// OCSPResponse ::= SEQUENCE {
// responseStatus OCSPResponseStatus,
// responseBytes [0] EXPLICIT ResponseBytes OPTIONAL }
// OCSPResponseStatus ::= ENUMERATED {
// successful (0), -- Response has valid confirmations
// malformedRequest (1), -- Illegal confirmation request
// internalError (2), -- Internal error in issuer
// tryLater (3), -- Try again later
// -- (4) is not used
// sigRequired (5), -- Must sign the request
// unauthorized (6) -- Request unauthorized
// }
ByteString reponseStatusValue;
reponseStatusValue.push_back(context.responseStatus);
ByteString responseStatus(TLV(der::ENUMERATED, reponseStatusValue));
if (responseStatus == ENCODING_FAILED) {
return ENCODING_FAILED;
}
ByteString responseBytesNested;
if (!context.skipResponseBytes) {
ByteString responseBytes(ResponseBytes(context));
if (responseBytes == ENCODING_FAILED) {
return ENCODING_FAILED;
}
responseBytesNested = TLV(der::CONSTRUCTED | der::CONTEXT_SPECIFIC,
responseBytes);
if (responseBytesNested == ENCODING_FAILED) {
return ENCODING_FAILED;
}
}
ByteString value;
value.append(responseStatus);
value.append(responseBytesNested);
ByteString result(TLV(der::SEQUENCE, value));
if (result == ENCODING_FAILED) {
return ENCODING_FAILED;
}
MaybeLogOutput(result, "ocsp");
return result;
}
// ResponseBytes ::= SEQUENCE {
// responseType OBJECT IDENTIFIER,
// response OCTET STRING }
ByteString
ResponseBytes(OCSPResponseContext& context)
{
// Includes tag and length
static const uint8_t id_pkix_ocsp_basic_encoded[] = {
0x06, 0x09, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x01
};
ByteString response(BasicOCSPResponse(context));
if (response == ENCODING_FAILED) {
return ENCODING_FAILED;
}
ByteString responseNested = TLV(der::OCTET_STRING, response);
if (responseNested == ENCODING_FAILED) {
return ENCODING_FAILED;
}
ByteString value;
value.append(id_pkix_ocsp_basic_encoded,
sizeof(id_pkix_ocsp_basic_encoded));
value.append(responseNested);
return TLV(der::SEQUENCE, value);
}
// BasicOCSPResponse ::= SEQUENCE {
// tbsResponseData ResponseData,
// signatureAlgorithm AlgorithmIdentifier,
// signature BIT STRING,
// certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
ByteString
BasicOCSPResponse(OCSPResponseContext& context)
{
ByteString tbsResponseData(ResponseData(context));
if (tbsResponseData == ENCODING_FAILED) {
return ENCODING_FAILED;
}
// TODO(bug 980538): certs
return SignedData(tbsResponseData, *context.signerKeyPair,
SignatureAlgorithm::rsa_pkcs1_with_sha256,
context.badSignature, context.certs);
}
// Extension ::= SEQUENCE {
// id OBJECT IDENTIFIER,
// critical BOOLEAN DEFAULT FALSE
// value OCTET STRING
// }
static ByteString
OCSPExtension(OCSPResponseContext& context, OCSPResponseExtension& extension)
{
ByteString encoded;
encoded.append(extension.id);
if (extension.critical) {
ByteString critical(Boolean(true));
if (critical == ENCODING_FAILED) {
return ENCODING_FAILED;
}
encoded.append(critical);
}
ByteString value(TLV(der::OCTET_STRING, extension.value));
if (value == ENCODING_FAILED) {
return ENCODING_FAILED;
}
encoded.append(value);
return TLV(der::SEQUENCE, encoded);
}
// Extensions ::= [1] {
// SEQUENCE OF Extension
// }
static ByteString
Extensions(OCSPResponseContext& context)
{
ByteString value;
for (OCSPResponseExtension* extension = context.extensions;
extension; extension = extension->next) {
ByteString extensionEncoded(OCSPExtension(context, *extension));
if (extensionEncoded == ENCODING_FAILED) {
return ENCODING_FAILED;
}
value.append(extensionEncoded);
}
ByteString sequence(TLV(der::SEQUENCE, value));
if (sequence == ENCODING_FAILED) {
return ENCODING_FAILED;
}
return TLV(der::CONSTRUCTED | der::CONTEXT_SPECIFIC | 1, sequence);
}
// ResponseData ::= SEQUENCE {
// version [0] EXPLICIT Version DEFAULT v1,
// responderID ResponderID,
// producedAt GeneralizedTime,
// responses SEQUENCE OF SingleResponse,
// responseExtensions [1] EXPLICIT Extensions OPTIONAL }
ByteString
ResponseData(OCSPResponseContext& context)
{
ByteString responderID(ResponderID(context));
if (responderID == ENCODING_FAILED) {
return ENCODING_FAILED;
}
ByteString producedAtEncoded(TimeToGeneralizedTime(context.producedAt));
if (producedAtEncoded == ENCODING_FAILED) {
return ENCODING_FAILED;
}
ByteString response(SingleResponse(context));
if (response == ENCODING_FAILED) {
return ENCODING_FAILED;
}
ByteString responses(TLV(der::SEQUENCE, response));
if (responses == ENCODING_FAILED) {
return ENCODING_FAILED;
}
ByteString responseExtensions;
if (context.extensions || context.includeEmptyExtensions) {
responseExtensions = Extensions(context);
}
ByteString value;
value.append(responderID);
value.append(producedAtEncoded);
value.append(responses);
value.append(responseExtensions);
return TLV(der::SEQUENCE, value);
}
// ResponderID ::= CHOICE {
// byName [1] Name,
// byKey [2] KeyHash }
// }
ByteString
ResponderID(OCSPResponseContext& context)
{
ByteString contents;
uint8_t responderIDType;
if (!context.signerNameDER.empty()) {
contents = context.signerNameDER;
responderIDType = 1; // byName
} else {
contents = KeyHash(context.signerKeyPair->subjectPublicKey);
if (contents == ENCODING_FAILED) {
return ENCODING_FAILED;
}
responderIDType = 2; // byKey
}
return TLV(der::CONSTRUCTED | der::CONTEXT_SPECIFIC | responderIDType,
contents);
}
// KeyHash ::= OCTET STRING -- SHA-1 hash of responder's public key
// -- (i.e., the SHA-1 hash of the value of the
// -- BIT STRING subjectPublicKey [excluding
// -- the tag, length, and number of unused
// -- bits] in the responder's certificate)
ByteString
KeyHash(const ByteString& subjectPublicKey)
{
return HashedOctetString(subjectPublicKey);
}
// SingleResponse ::= SEQUENCE {
// certID CertID,
// certStatus CertStatus,
// thisUpdate GeneralizedTime,
// nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL,
// singleExtensions [1] EXPLICIT Extensions OPTIONAL }
ByteString
SingleResponse(OCSPResponseContext& context)
{
ByteString certID(CertID(context));
if (certID == ENCODING_FAILED) {
return ENCODING_FAILED;
}
ByteString certStatus(CertStatus(context));
if (certStatus == ENCODING_FAILED) {
return ENCODING_FAILED;
}
ByteString thisUpdateEncoded(TimeToGeneralizedTime(context.thisUpdate));
if (thisUpdateEncoded == ENCODING_FAILED) {
return ENCODING_FAILED;
}
ByteString nextUpdateEncodedNested;
if (context.includeNextUpdate) {
ByteString nextUpdateEncoded(TimeToGeneralizedTime(context.nextUpdate));
if (nextUpdateEncoded == ENCODING_FAILED) {
return ENCODING_FAILED;
}
nextUpdateEncodedNested = TLV(der::CONSTRUCTED | der::CONTEXT_SPECIFIC | 0,
nextUpdateEncoded);
if (nextUpdateEncodedNested == ENCODING_FAILED) {
return ENCODING_FAILED;
}
}
ByteString value;
value.append(certID);
value.append(certStatus);
value.append(thisUpdateEncoded);
value.append(nextUpdateEncodedNested);
return TLV(der::SEQUENCE, value);
}
// CertID ::= SEQUENCE {
// hashAlgorithm AlgorithmIdentifier,
// issuerNameHash OCTET STRING, -- Hash of issuer's DN
// issuerKeyHash OCTET STRING, -- Hash of issuer's public key
// serialNumber CertificateSerialNumber }
ByteString
CertID(OCSPResponseContext& context)
{
ByteString issuerName(context.certID.issuer.UnsafeGetData(),
context.certID.issuer.GetLength());
ByteString issuerNameHash(HashedOctetString(issuerName));
if (issuerNameHash == ENCODING_FAILED) {
return ENCODING_FAILED;
}
ByteString issuerKeyHash;
{
// context.certID.issuerSubjectPublicKeyInfo is the entire
// SubjectPublicKeyInfo structure, but we need just the subjectPublicKey
// part.
Reader input(context.certID.issuerSubjectPublicKeyInfo);
Reader contents;
if (der::ExpectTagAndGetValue(input, der::SEQUENCE, contents) != Success) {
return ENCODING_FAILED;
}
// Skip AlgorithmIdentifier
if (der::ExpectTagAndSkipValue(contents, der::SEQUENCE) != Success) {
return ENCODING_FAILED;
}
Input subjectPublicKey;
if (der::BitStringWithNoUnusedBits(contents, subjectPublicKey)
!= Success) {
return ENCODING_FAILED;
}
issuerKeyHash = KeyHash(ByteString(subjectPublicKey.UnsafeGetData(),
subjectPublicKey.GetLength()));
if (issuerKeyHash == ENCODING_FAILED) {
return ENCODING_FAILED;
}
}
ByteString serialNumberValue(context.certID.serialNumber.UnsafeGetData(),
context.certID.serialNumber.GetLength());
ByteString serialNumber(TLV(der::INTEGER, serialNumberValue));
if (serialNumber == ENCODING_FAILED) {
return ENCODING_FAILED;
}
// python DottedOIDToCode.py --alg id-sha1 1.3.14.3.2.26
static const uint8_t alg_id_sha1[] = {
0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a
};
ByteString value;
value.append(alg_id_sha1, sizeof(alg_id_sha1));
value.append(issuerNameHash);
value.append(issuerKeyHash);
value.append(serialNumber);
return TLV(der::SEQUENCE, value);
}
// CertStatus ::= CHOICE {
// good [0] IMPLICIT NULL,
// revoked [1] IMPLICIT RevokedInfo,
// unknown [2] IMPLICIT UnknownInfo }
//
// RevokedInfo ::= SEQUENCE {
// revocationTime GeneralizedTime,
// revocationReason [0] EXPLICIT CRLReason OPTIONAL }
//
// UnknownInfo ::= NULL
//
ByteString
CertStatus(OCSPResponseContext& context)
{
switch (context.certStatus) {
// Both good and unknown are ultimately represented as NULL - the only
// difference is in the tag that identifies them.
case 0:
case 2:
{
return TLV(der::CONTEXT_SPECIFIC | context.certStatus, ByteString());
}
case 1:
{
ByteString revocationTime(TimeToGeneralizedTime(context.revocationTime));
if (revocationTime == ENCODING_FAILED) {
return ENCODING_FAILED;
}
// TODO(bug 980536): add support for revocationReason
return TLV(der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1, revocationTime);
}
default:
assert(false);
// fall through
}
return ENCODING_FAILED;
}
} } } // namespace mozilla::pkix::test