bug 1178988 - convert test_ocsp_url to generate certificates at build time r=Cykesiopka

Also enable loading of certificates and private keys into GenerateOCSPResponse
This commit is contained in:
David Keeler 2015-06-04 17:03:48 -07:00
parent 893b8954ec
commit 359bd103d4
39 changed files with 388 additions and 61 deletions

View File

@ -12,6 +12,7 @@ TEST_DIRS += [
'test_cert_version',
'test_intermediate_basic_usage_constraints',
'test_pinning_dynamic',
'test_ocsp_url',
]
if not CONFIG['MOZ_NO_SMART_CARDS']:

View File

@ -26,6 +26,7 @@ extKeyUsage:[serverAuth,clientAuth,codeSigning,emailProtection
nsSGC, # Netscape Server Gated Crypto
OCSPSigning,timeStamping]
subjectAlternativeName:[<dNSName>,...]
authorityInformationAccess:<OCSP URI>
Where:
[] indicates an optional field or component of a field
@ -120,6 +121,18 @@ def getASN1Tag(asn1Type):
type from the pyasn1 package"""
return asn1Type.baseTagSet.getBaseTag().asTuple()[2]
def stringToAccessDescription(string):
"""Helper function that takes a string representing a URI
presumably identifying an OCSP authority information access
location. Returns an AccessDescription usable by pyasn1."""
accessMethod = rfc2459.id_ad_ocsp
accessLocation = rfc2459.GeneralName()
accessLocation.setComponentByName('uniformResourceIdentifier', string)
sequence = univ.Sequence()
sequence.setComponentByPosition(0, accessMethod)
sequence.setComponentByPosition(1, accessLocation)
return sequence
def stringToAlgorithmIdentifier(string):
"""Helper function that converts a description of an algorithm
to a representation usable by the pyasn1 package"""
@ -247,6 +260,8 @@ class Certificate:
self.addExtKeyUsage(value)
elif extensionType == 'subjectAlternativeName':
self.addSubjectAlternativeName(value)
elif extensionType == 'authorityInformationAccess':
self.addAuthorityInformationAccess(value)
else:
raise UnknownExtensionTypeError(extensionType)
@ -321,6 +336,12 @@ class Certificate:
count += 1
self.addExtension(rfc2459.id_ce_subjectAltName, subjectAlternativeName)
def addAuthorityInformationAccess(self, ocspURI):
sequence = univ.Sequence()
accessDescription = stringToAccessDescription(ocspURI)
sequence.setComponentByPosition(0, accessDescription)
self.addExtension(rfc2459.id_pe_authorityInfoAccess, sequence)
def getVersion(self):
return rfc2459.Version(self.versionValue).subtype(
explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))

View File

@ -5,8 +5,10 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
"""
Provides methods for signing data and representing a key as a
subject public key info for use with pyasn1.
Reads a key specification from stdin or a file and outputs a
PKCS #8 file representing the (private) key. Also provides
methods for signing data and representing the key as a subject
public key info for use with pyasn1.
The key specification format is currently very simple. If it is
empty, one RSA key is used. If it consists of the string
@ -18,8 +20,10 @@ strength, signature algorithm, etc.).
from pyasn1.codec.der import encoder
from pyasn1.type import univ, namedtype
from pyasn1_modules import rfc2459
import base64
import binascii
import rsa
import sys
def byteStringToHexifiedBitString(string):
"""Takes a string of bytes and returns a hex string representing
@ -53,6 +57,30 @@ class RSAPublicKey(univ.Sequence):
namedtype.NamedType('E', univ.Integer()))
class RSAPrivateKey(univ.Sequence):
"""Helper type for encoding an RSA private key"""
componentType = namedtype.NamedTypes(
namedtype.NamedType('version', univ.Integer()),
namedtype.NamedType('modulus', univ.Integer()),
namedtype.NamedType('publicExponent', univ.Integer()),
namedtype.NamedType('privateExponent', univ.Integer()),
namedtype.NamedType('prime1', univ.Integer()),
namedtype.NamedType('prime2', univ.Integer()),
namedtype.NamedType('exponent1', univ.Integer()),
namedtype.NamedType('exponent2', univ.Integer()),
namedtype.NamedType('coefficient', univ.Integer()),
)
class PrivateKeyInfo(univ.Sequence):
"""Helper type for encoding a PKCS #8 private key info"""
componentType = namedtype.NamedTypes(
namedtype.NamedType('version', univ.Integer()),
namedtype.NamedType('privateKeyAlgorithm', rfc2459.AlgorithmIdentifier()),
namedtype.NamedType('privateKey', univ.OctetString())
)
class RSAKey:
# For reference, when encoded as a subject public key info, the
# base64-encoded sha-256 hash of this key is
@ -186,6 +214,37 @@ class RSAKey:
else:
raise UnknownKeySpecificationError(specification)
def toDER(self):
privateKeyInfo = PrivateKeyInfo()
privateKeyInfo.setComponentByName('version', 0)
algorithmIdentifier = rfc2459.AlgorithmIdentifier()
algorithmIdentifier.setComponentByName('algorithm', rfc2459.rsaEncryption)
algorithmIdentifier.setComponentByName('parameters', univ.Null())
privateKeyInfo.setComponentByName('privateKeyAlgorithm', algorithmIdentifier)
rsaPrivateKey = RSAPrivateKey()
rsaPrivateKey.setComponentByName('version', 0)
rsaPrivateKey.setComponentByName('modulus', self.RSA_N)
rsaPrivateKey.setComponentByName('publicExponent', self.RSA_E)
rsaPrivateKey.setComponentByName('privateExponent', self.RSA_D)
rsaPrivateKey.setComponentByName('prime1', self.RSA_P)
rsaPrivateKey.setComponentByName('prime2', self.RSA_Q)
rsaPrivateKey.setComponentByName('exponent1', self.RSA_exp1)
rsaPrivateKey.setComponentByName('exponent2', self.RSA_exp2)
rsaPrivateKey.setComponentByName('coefficient', self.RSA_coef)
rsaPrivateKeyEncoded = encoder.encode(rsaPrivateKey)
privateKeyInfo.setComponentByName('privateKey', univ.OctetString(rsaPrivateKeyEncoded))
return encoder.encode(privateKeyInfo)
def toPEM(self):
output = '-----BEGIN PRIVATE KEY-----'
der = self.toDER()
b64 = base64.b64encode(der)
while b64:
output += '\n' + b64[:64]
b64 = b64[64:]
output += '\n-----END PRIVATE KEY-----'
return output
def asSubjectPublicKeyInfo(self):
"""Returns a subject public key info representing
this key for use by pyasn1."""
@ -208,3 +267,16 @@ class RSAKey:
rsaPrivateKey = rsa.PrivateKey(self.RSA_N, self.RSA_E, self.RSA_D, self.RSA_P, self.RSA_Q)
signature = rsa.sign(data, rsaPrivateKey, 'SHA-256')
return byteStringToHexifiedBitString(signature)
# The build harness will call this function with an output file-like
# object and a path to a file containing a specification. This will
# read the specification and output the key as ASCII-encoded PKCS #8.
def main(output, inputPath):
with open(inputPath) as configStream:
output.write(RSAKey(configStream.read()).toPEM())
# When run as a standalone program, this will read a specification from
# stdin and output the certificate as PEM to stdout.
if __name__ == '__main__':
print RSAKey(sys.stdin.read()).toPEM()

View File

@ -25,14 +25,14 @@ function start_ocsp_responder(expectedCertNames, expectedPaths) {
}
function check_cert_err(cert_name, expected_error) {
let cert = constructCertFromFile("test_ocsp_url/" + cert_name + ".der");
let cert = constructCertFromFile("test_ocsp_url/" + cert_name + ".pem");
return checkCertErrorGeneric(certdb, cert, expected_error,
certificateUsageSSLServer);
}
function run_test() {
addCertFromFile(certdb, "test_ocsp_url/ca.der", 'CTu,CTu,CTu');
addCertFromFile(certdb, "test_ocsp_url/int.der", ',,');
addCertFromFile(certdb, "test_ocsp_url/ca.pem", 'CTu,CTu,CTu');
addCertFromFile(certdb, "test_ocsp_url/int.pem", ',,');
// Enabled so that we can force ocsp failure responses.
Services.prefs.setBoolPref("security.OCSP.require", true);
@ -44,7 +44,7 @@ function run_test() {
add_test(function() {
clearOCSPCache();
let ocspResponder = failingOCSPResponder();
check_cert_err("bad-scheme",SEC_ERROR_CERT_BAD_ACCESS_LOCATION);
check_cert_err("bad-scheme", SEC_ERROR_CERT_BAD_ACCESS_LOCATION);
ocspResponder.stop(run_next_test);
});

View File

@ -0,0 +1,3 @@
issuer:int
subject:bad-scheme
extension:authorityInformationAccess:/www.example.com

View File

@ -0,0 +1,4 @@
issuer:ca
subject:ca
extension:basicConstraints:cA,
extension:keyUsage:keyCertSign,cRLSign

View File

@ -0,0 +1,3 @@
issuer:int
subject:empty-port
extension:authorityInformationAccess:http://www.example.com:/

View File

@ -0,0 +1,3 @@
issuer:int
subject:empty-scheme-url
extension:authorityInformationAccess:://www.example.com:8888/

View File

@ -0,0 +1,3 @@
issuer:int
subject:ftp-url
extension:authorityInformationAccess:ftp://www.example.com:8888/

View File

@ -1,40 +0,0 @@
#!/usr/bin/python
import tempfile, os, sys
libpath = os.path.abspath('../psm_common_py')
sys.path.append(libpath)
import CertUtils
srcdir = os.getcwd()
db = tempfile.mkdtemp()
def generate_ca_cert(db_dir, dest_dir, noise_file, name):
return CertUtils.generate_ca_cert(db_dir, dest_dir, noise_file, name,
3, True)
def generate_child_cert(db_dir, dest_dir, noise_file, name, ca_nick, is_ee,
ocsp_url):
return CertUtils.generate_child_cert(db_dir, dest_dir, noise_file, name,
ca_nick, 3, True, is_ee, ocsp_url)
def generate_certs():
[noise_file, pwd_file] = CertUtils.init_nss_db(srcdir)
generate_ca_cert(srcdir, srcdir, noise_file, 'ca')
generate_child_cert(srcdir, srcdir, noise_file, 'int', 'ca', False, '')
nick_baseurl = { 'no-path-url': "http://www.example.com:8888",
'ftp-url': "ftp://www.example.com:8888/",
'no-scheme-url': "www.example.com:8888/",
'empty-scheme-url': "://www.example.com:8888/",
'no-host-url': "http://:8888/",
'hTTp-url': "hTTp://www.example.com:8888/hTTp-url",
'https-url': "https://www.example.com:8888/https-url",
'bad-scheme': "/www.example.com",
'empty-port': "http://www.example.com:/",
'unknown-scheme': "ttp://www.example.com",
'negative-port': "http://www.example.com:-1",
'no-scheme-host-port': "/" }
for nick, url in nick_baseurl.iteritems():
generate_child_cert(srcdir, srcdir, noise_file, nick, 'int', True, url)
generate_certs()

View File

@ -0,0 +1,3 @@
issuer:int
subject:hTTp-url
extension:authorityInformationAccess:hTTp://www.example.com:8888/hTTp-url

View File

@ -0,0 +1,3 @@
issuer:int
subject:https-url
extension:authorityInformationAccess:https://www.example.com:8888/https-url

View File

@ -0,0 +1,4 @@
issuer:ca
subject:int
extension:basicConstraints:cA,
extension:keyUsage:keyCertSign,cRLSign

View File

@ -0,0 +1,42 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
test_certificates = (
'bad-scheme.pem',
'ca.pem',
'empty-port.pem',
'empty-scheme-url.pem',
'ftp-url.pem',
'hTTp-url.pem',
'https-url.pem',
'int.pem',
'negative-port.pem',
'no-host-url.pem',
'no-path-url.pem',
'no-scheme-host-port.pem',
'no-scheme-url.pem',
'unknown-scheme.pem',
)
for test_certificate in test_certificates:
input_file = test_certificate + '.certspec'
GENERATED_FILES += [test_certificate]
props = GENERATED_FILES[test_certificate]
props.script = '../pycert.py'
props.inputs = [input_file, '!/config/buildid']
TEST_HARNESS_FILES.xpcshell.security.manager.ssl.tests.unit.test_ocsp_url += ['!%s' % test_certificate]
test_keys = (
'int.key',
)
for test_key in test_keys:
input_file = test_key + '.keyspec'
GENERATED_FILES += [test_key]
props = GENERATED_FILES[test_key]
props.script = '../pykey.py'
props.inputs = [input_file]
TEST_HARNESS_FILES.xpcshell.security.manager.ssl.tests.unit.test_ocsp_url += ['!%s' % test_key]

View File

@ -0,0 +1,3 @@
issuer:int
subject:negative-port
extension:authorityInformationAccess:http://www.example.com:-1

View File

@ -0,0 +1,3 @@
issuer:int
subject:no-host-url
extension:authorityInformationAccess:http://:8888/

View File

@ -0,0 +1,3 @@
issuer:int
subject:no-path-url
extension:authorityInformationAccess:http://www.example.com:8888

View File

@ -0,0 +1,3 @@
issuer:int
subject:no-scheme-host-port
extension:authorityInformationAccess:/

View File

@ -0,0 +1,3 @@
issuer:int
subject:no-scheme-url
extension:authorityInformationAccess:www.example.com:8888/

View File

@ -1,5 +0,0 @@
library=
name=NSS Internal PKCS #11 Module
parameters=configdir='sql:./security/manager/ssl/tests/unit/test_ocsp_url' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})

View File

@ -0,0 +1,3 @@
issuer:int
subject:unknown-scheme-url
extension:authorityInformationAccess:ttp://www.example.com

View File

@ -13,6 +13,8 @@
#include "mozilla/ArrayUtils.h"
#include "base64.h"
#include "cert.h"
#include "nspr.h"
#include "nss.h"
#include "plarenas.h"
@ -66,9 +68,8 @@ const static OCSPResponseName kOCSPResponseNameList[] = {
// two years old
};
bool
stringToOCSPResponseType(const char* respText,
StringToOCSPResponseType(const char* respText,
/*out*/ OCSPResponseType* OCSPType)
{
if (!OCSPType) {
@ -107,7 +108,201 @@ WriteResponse(const char* filename, const SECItem* item)
return true;
}
template <size_t N>
SECStatus
ReadFileToBuffer(const char* basePath, const char* filename, char (&buf)[N])
{
static_assert(N > 0, "input buffer too small for ReadFileToBuffer");
if (PR_snprintf(buf, N - 1, "%s/%s", basePath, filename) == 0) {
PrintPRError("PR_snprintf failed");
return SECFailure;
}
ScopedPRFileDesc fd(PR_OpenFile(buf, PR_RDONLY, 0));
if (!fd) {
PrintPRError("PR_Open failed");
return SECFailure;
}
int32_t fileSize = PR_Available(fd);
if (fileSize < 0) {
PrintPRError("PR_Available failed");
return SECFailure;
}
if (static_cast<size_t>(fileSize) > N - 1) {
PR_fprintf(PR_STDERR, "file too large - not reading\n");
return SECFailure;
}
int32_t bytesRead = PR_Read(fd, buf, fileSize);
if (bytesRead != fileSize) {
PrintPRError("PR_Read failed");
return SECFailure;
}
buf[bytesRead] = 0;
return SECSuccess;
}
namespace mozilla {
MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRDir, PRDir, PR_CloseDir);
MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPORTString, unsigned char, PORT_Free);
};
void
AddKeyFromFile(const char* basePath, const char* filename)
{
const char* PRIVATE_KEY_HEADER = "-----BEGIN PRIVATE KEY-----";
const char* PRIVATE_KEY_FOOTER = "-----END PRIVATE KEY-----";
char buf[16384] = { 0 };
SECStatus rv = ReadFileToBuffer(basePath, filename, buf);
if (rv != SECSuccess) {
return;
}
if (strncmp(buf, PRIVATE_KEY_HEADER, strlen(PRIVATE_KEY_HEADER)) != 0) {
PR_fprintf(PR_STDERR, "invalid key - not importing\n");
return;
}
const char* bufPtr = buf + strlen(PRIVATE_KEY_HEADER);
size_t bufLen = strlen(buf);
char base64[16384] = { 0 };
char* base64Ptr = base64;
while (bufPtr < buf + bufLen) {
if (strncmp(bufPtr, PRIVATE_KEY_FOOTER, strlen(PRIVATE_KEY_FOOTER)) == 0) {
break;
}
if (*bufPtr != '\r' && *bufPtr != '\n') {
*base64Ptr = *bufPtr;
base64Ptr++;
}
bufPtr++;
}
unsigned int binLength;
ScopedPORTString bin(ATOB_AsciiToData(base64, &binLength));
if (!bin || binLength == 0) {
PrintPRError("ATOB_AsciiToData failed");
return;
}
ScopedSECItem secitem(SECITEM_AllocItem(nullptr, nullptr, binLength));
if (!secitem) {
PrintPRError("SECITEM_AllocItem failed");
return;
}
memcpy(secitem->data, bin, binLength);
ScopedPK11SlotInfo slot(PK11_GetInternalKeySlot());
if (!slot) {
PrintPRError("PK11_GetInternalKeySlot failed");
return;
}
if (PK11_NeedUserInit(slot)) {
if (PK11_InitPin(slot, nullptr, nullptr) != SECSuccess) {
PrintPRError("PK11_InitPin failed");
return;
}
}
SECKEYPrivateKey* privateKey;
if (PK11_ImportDERPrivateKeyInfoAndReturnKey(slot, secitem, nullptr, nullptr,
true, false, KU_ALL,
&privateKey, nullptr)
!= SECSuccess) {
PrintPRError("PK11_ImportDERPrivateKeyInfoAndReturnKey failed");
return;
}
SECKEY_DestroyPrivateKey(privateKey);
}
SECStatus
DecodeCertCallback(void* arg, SECItem** certs, int numcerts)
{
if (numcerts != 1) {
PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
return SECFailure;
}
SECItem* certDEROut = static_cast<SECItem*>(arg);
return SECITEM_CopyItem(nullptr, certDEROut, *certs);
}
void
AddCertificateFromFile(const char* basePath, const char* filename)
{
char buf[16384] = { 0 };
SECStatus rv = ReadFileToBuffer(basePath, filename, buf);
if (rv != SECSuccess) {
return;
}
SECItem certDER;
rv = CERT_DecodeCertPackage(buf, strlen(buf), DecodeCertCallback, &certDER);
if (rv != SECSuccess) {
PrintPRError("CERT_DecodeCertPackage failed");
return;
}
ScopedCERTCertificate cert(CERT_NewTempCertificate(CERT_GetDefaultCertDB(),
&certDER, nullptr, false,
true));
PORT_Free(certDER.data);
if (!cert) {
PrintPRError("CERT_NewTempCertificate failed");
return;
}
const char* extension = strstr(filename, ".pem");
if (!extension) {
PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
return;
}
size_t nicknameLength = extension - filename;
memset(buf, 0, sizeof(buf));
memcpy(buf, filename, nicknameLength);
buf[nicknameLength] = 0;
ScopedPK11SlotInfo slot(PK11_GetInternalKeySlot());
if (!slot) {
PrintPRError("PK11_GetInternalKeySlot failed");
return;
}
rv = PK11_ImportCert(slot, cert, CK_INVALID_HANDLE, buf, false);
if (rv != SECSuccess) {
PrintPRError("PK11_ImportCert failed");
}
}
SECStatus
InitializeNSS(const char* nssCertDBDir)
{
// First attempt to initialize NSS in read-only mode, in case the specified
// directory contains NSS DBs that are tracked by revision control.
// If this succeeds, we're done.
if (NSS_Initialize(nssCertDBDir, "", "", SECMOD_DB, NSS_INIT_READONLY)
== SECSuccess) {
return SECSuccess;
}
// Otherwise, create a new read-write DB and load all .pem and .key files.
if (NSS_Initialize(nssCertDBDir, "", "", SECMOD_DB, 0) != SECSuccess) {
PrintPRError("NSS_Initialize failed");
return SECFailure;
}
const char* basePath = nssCertDBDir;
// The NSS cert DB path could have been specified as "sql:path". Trim off
// the leading "sql:" if so.
if (strncmp(basePath, "sql:", 4) == 0) {
basePath = basePath + 4;
}
ScopedPRDir fdDir(PR_OpenDir(basePath));
if (!fdDir) {
PrintPRError("PR_OpenDir failed");
return SECFailure;
}
for (PRDirEntry* dirEntry = PR_ReadDir(fdDir, PR_SKIP_BOTH); dirEntry;
dirEntry = PR_ReadDir(fdDir, PR_SKIP_BOTH)) {
size_t nameLength = strlen(dirEntry->name);
if (nameLength > 4) {
if (strncmp(dirEntry->name + nameLength - 4, ".pem", 4) == 0) {
AddCertificateFromFile(basePath, dirEntry->name);
} else if (strncmp(dirEntry->name + nameLength - 4, ".key", 4) == 0) {
AddKeyFromFile(basePath, dirEntry->name);
}
}
}
return SECSuccess;
}
int
main(int argc, char* argv[])
@ -120,12 +315,9 @@ main(int argc, char* argv[])
argv[0]);
exit(EXIT_FAILURE);
}
const char* dbdir = argv[1];
SECStatus rv;
rv = NSS_Init(dbdir);
SECStatus rv = InitializeNSS(argv[1]);
if (rv != SECSuccess) {
PrintPRError("Failed to initialize NSS");
PR_fprintf(PR_STDERR, "Failed to initialize NSS\n");
exit(EXIT_FAILURE);
}
PLArenaPool* arena = PORT_NewArena(256 * argc);
@ -141,15 +333,15 @@ main(int argc, char* argv[])
const char* filename = argv[i + 3];
OCSPResponseType ORT;
if (!stringToOCSPResponseType(ocspTypeText, &ORT)) {
if (!StringToOCSPResponseType(ocspTypeText, &ORT)) {
PR_fprintf(PR_STDERR, "Cannot generate OCSP response of type %s\n",
ocspTypeText);
exit(EXIT_FAILURE);
}
ScopedCERTCertificate cert;
cert = PK11_FindCertFromNickname(certNick, nullptr);
ScopedCERTCertificate cert(PK11_FindCertFromNickname(certNick, nullptr));
if (!cert) {
PrintPRError("PK11_FindCertFromNickname failed");
PR_fprintf(PR_STDERR, "Failed to find certificate with nick '%s'\n",
certNick);
exit(EXIT_FAILURE);