mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-26 20:30:41 +00:00
Bug 1815768 - enable automatically regenerating test PKCS12 files r=jschanck
This adds a rudimentary method of regenerating test PKCS12 files via `mach generate-test-certs`. Due to the complicated nature of the format, this implementation ultimately relies on OpenSSL to implement the encryption and encoding. genpgocert.py already relies on OpenSSL, so this is not a new requirement. Additionally, due to the limited number of test PKCS12 files in the tree, the options for creating these files are not very sophisticated. In the future, it may be beneficial to create more kinds of files with various properties, but at the moment this suffices. Differential Revision: https://phabricator.services.mozilla.com/D172192
This commit is contained in:
parent
692d5b49d3
commit
edd47623df
Binary file not shown.
3
netwerk/test/unit/client-cert.p12.pkcs12spec
Normal file
3
netwerk/test/unit/client-cert.p12.pkcs12spec
Normal file
@ -0,0 +1,3 @@
|
||||
issuer:Test CA
|
||||
subject:Test End-entity
|
||||
extension:subjectAlternativeName:example.com
|
@ -10,7 +10,7 @@ from mozpack.files import FileFinder
|
||||
from mozpack.path import basedir
|
||||
|
||||
|
||||
def run_module_main_on(module, input_filename):
|
||||
def run_module_main_on(module, input_filename, output_is_binary):
|
||||
"""Run the given module (pycert or pykey) on the given
|
||||
file."""
|
||||
# By convention, the specification files have names of the form
|
||||
@ -19,7 +19,14 @@ def run_module_main_on(module, input_filename):
|
||||
# (certspec or keyspec). Taking off the ".*spec" part results in the
|
||||
# desired filename for this file.
|
||||
output_filename = os.path.splitext(input_filename)[0]
|
||||
with open(output_filename, mode="w", encoding="utf-8", newline="\n") as output:
|
||||
mode = "w"
|
||||
encoding = "utf-8"
|
||||
newline = "\n"
|
||||
if output_is_binary:
|
||||
mode = "wb"
|
||||
encoding = None
|
||||
newline = None
|
||||
with open(output_filename, mode=mode, encoding=encoding, newline=newline) as output:
|
||||
module.main(output, input_filename)
|
||||
|
||||
|
||||
@ -35,10 +42,20 @@ def is_keyspec_file(filename):
|
||||
return filename.endswith(".keyspec")
|
||||
|
||||
|
||||
def is_pkcs12spec_file(filename):
|
||||
"""Returns True if the given filename is a pkcs12
|
||||
specification file (.pkcs12spec) and False otherwise."""
|
||||
return filename.endswith(".pkcs12spec")
|
||||
|
||||
|
||||
def is_specification_file(filename):
|
||||
"""Returns True if the given filename is a specification
|
||||
file supported by this script, and False otherewise."""
|
||||
return is_certspec_file(filename) or is_keyspec_file(filename)
|
||||
return (
|
||||
is_certspec_file(filename)
|
||||
or is_keyspec_file(filename)
|
||||
or is_pkcs12spec_file(filename)
|
||||
)
|
||||
|
||||
|
||||
def is_excluded_directory(directory, exclusions):
|
||||
@ -66,20 +83,27 @@ def generate_test_certs(command_context, specifications):
|
||||
"""Generate test certificates and keys from specifications."""
|
||||
import pycert
|
||||
import pykey
|
||||
import pypkcs12
|
||||
|
||||
if not specifications:
|
||||
specifications = find_all_specifications(command_context)
|
||||
|
||||
for specification in specifications:
|
||||
output_is_binary = False
|
||||
if is_certspec_file(specification):
|
||||
module = pycert
|
||||
elif is_keyspec_file(specification):
|
||||
module = pykey
|
||||
elif is_pkcs12spec_file(specification):
|
||||
module = pypkcs12
|
||||
output_is_binary = True
|
||||
else:
|
||||
raise UserError(
|
||||
"'{}' is not a .certspec or .keyspec file".format(specification)
|
||||
"'{}' is not a .certspec, .keyspec, or .pkcs12spec file".format(
|
||||
specification
|
||||
)
|
||||
)
|
||||
run_module_main_on(module, os.path.abspath(specification))
|
||||
run_module_main_on(module, os.path.abspath(specification), output_is_binary)
|
||||
return 0
|
||||
|
||||
|
||||
|
@ -794,15 +794,6 @@ class Certificate(object):
|
||||
# file-like object and a path to a file containing a
|
||||
# specification. This will read the specification and output
|
||||
# the certificate as PEM.
|
||||
# This utility tries as hard as possible to ensure that two
|
||||
# runs with the same input will have the same output. This is
|
||||
# particularly important when building on OS X, where we
|
||||
# generate everything twice for unified builds. During the
|
||||
# unification step, if any pair of input files differ, the build
|
||||
# system throws an error.
|
||||
# The one concrete failure mode is if one run happens before
|
||||
# midnight on New Year's Eve and the next run happens after
|
||||
# midnight.
|
||||
def main(output, inputPath):
|
||||
with open(inputPath) as configStream:
|
||||
output.write(Certificate(configStream).toPEM() + "\n")
|
||||
|
@ -946,8 +946,6 @@ def keyFromSpecification(specification):
|
||||
# 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(keyFromSpecification(configStream.read().strip()).toPEM() + "\n")
|
||||
|
124
security/manager/tools/pypkcs12.py
Normal file
124
security/manager/tools/pypkcs12.py
Normal file
@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env 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/.
|
||||
|
||||
"""
|
||||
Reads a specification from stdin or a file and outputs a PKCS12
|
||||
file with the desired properties.
|
||||
|
||||
The input format currently consists of a pycert certificate
|
||||
specification (see pycert.py).
|
||||
Currently, keys other than the default key are not supported.
|
||||
The password that is used to encrypt and authenticate the file
|
||||
is "password".
|
||||
"""
|
||||
|
||||
import base64
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from distutils.spawn import find_executable
|
||||
|
||||
import mozinfo
|
||||
import pycert
|
||||
import pykey
|
||||
import six
|
||||
from mozfile import NamedTemporaryFile
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
"""Base class for exceptions in this module."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class OpenSSLError(Error):
|
||||
"""Class for handling errors when calling OpenSSL."""
|
||||
|
||||
def __init__(self, status):
|
||||
super(OpenSSLError, self).__init__()
|
||||
self.status = status
|
||||
|
||||
def __str__(self):
|
||||
return "Error running openssl: %s " % self.status
|
||||
|
||||
|
||||
def runUtil(util, args):
|
||||
env = os.environ.copy()
|
||||
if mozinfo.os == "linux":
|
||||
pathvar = "LD_LIBRARY_PATH"
|
||||
app_path = os.path.dirname(util)
|
||||
if pathvar in env:
|
||||
env[pathvar] = "%s%s%s" % (app_path, os.pathsep, env[pathvar])
|
||||
else:
|
||||
env[pathvar] = app_path
|
||||
proc = subprocess.run(
|
||||
[util] + args,
|
||||
env=env,
|
||||
universal_newlines=True,
|
||||
)
|
||||
return proc.returncode
|
||||
|
||||
|
||||
class PKCS12(object):
|
||||
"""Utility class for reading a specification and generating
|
||||
a PKCS12 file"""
|
||||
|
||||
def __init__(self, paramStream):
|
||||
self.cert = pycert.Certificate(paramStream)
|
||||
self.key = pykey.keyFromSpecification("default")
|
||||
|
||||
def toDER(self):
|
||||
with NamedTemporaryFile(mode="wt+") as certTmp, NamedTemporaryFile(
|
||||
mode="wt+"
|
||||
) as keyTmp, NamedTemporaryFile(mode="rb+") as pkcs12Tmp:
|
||||
certTmp.write(self.cert.toPEM())
|
||||
certTmp.flush()
|
||||
keyTmp.write(self.key.toPEM())
|
||||
keyTmp.flush()
|
||||
openssl = find_executable("openssl")
|
||||
status = runUtil(
|
||||
openssl,
|
||||
[
|
||||
"pkcs12",
|
||||
"-export",
|
||||
"-inkey",
|
||||
keyTmp.name,
|
||||
"-in",
|
||||
certTmp.name,
|
||||
"-out",
|
||||
pkcs12Tmp.name,
|
||||
"-passout",
|
||||
"pass:password",
|
||||
],
|
||||
)
|
||||
if status != 0:
|
||||
raise OpenSSLError(status)
|
||||
return pkcs12Tmp.read()
|
||||
|
||||
def toPEM(self):
|
||||
output = "-----BEGIN PKCS12-----"
|
||||
der = self.toDER()
|
||||
b64 = six.ensure_text(base64.b64encode(der))
|
||||
while b64:
|
||||
output += "\n" + b64[:64]
|
||||
b64 = b64[64:]
|
||||
output += "\n-----END PKCS12-----"
|
||||
return output
|
||||
|
||||
|
||||
# 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 PKCS12 file.
|
||||
def main(output, inputPath):
|
||||
with open(inputPath) as configStream:
|
||||
output.write(PKCS12(configStream).toDER())
|
||||
|
||||
|
||||
# When run as a standalone program, this will read a specification from
|
||||
# stdin and output the PKCS12 file as PEM to stdout.
|
||||
if __name__ == "__main__":
|
||||
print(PKCS12(sys.stdin).toPEM())
|
Loading…
x
Reference in New Issue
Block a user