pypkcs12.py (3459B)
1 #!/usr/bin/env python 2 # 3 # This Source Code Form is subject to the terms of the Mozilla Public 4 # License, v. 2.0. If a copy of the MPL was not distributed with this 5 # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 7 """ 8 Reads a specification from stdin or a file and outputs a PKCS12 9 file with the desired properties. 10 11 The input format currently consists of a pycert certificate 12 specification (see pycert.py). 13 Currently, keys other than the default key are not supported. 14 The password that is used to encrypt and authenticate the file 15 is "password". 16 """ 17 18 import base64 19 import os 20 import shutil 21 import subprocess 22 import sys 23 24 import mozinfo 25 import pycert 26 import pykey 27 from mozfile import NamedTemporaryFile 28 29 30 class Error(Exception): 31 """Base class for exceptions in this module.""" 32 33 pass 34 35 36 class OpenSSLError(Error): 37 """Class for handling errors when calling OpenSSL.""" 38 39 def __init__(self, status): 40 super().__init__() 41 self.status = status 42 43 def __str__(self): 44 return "Error running openssl: %s " % self.status 45 46 47 def runUtil(util, args): 48 env = os.environ.copy() 49 if mozinfo.os == "linux": 50 pathvar = "LD_LIBRARY_PATH" 51 app_path = os.path.dirname(util) 52 if pathvar in env: 53 env[pathvar] = "%s%s%s" % (app_path, os.pathsep, env[pathvar]) 54 else: 55 env[pathvar] = app_path 56 proc = subprocess.run( 57 [util] + args, 58 check=False, 59 env=env, 60 text=True, 61 ) 62 return proc.returncode 63 64 65 class PKCS12: 66 """Utility class for reading a specification and generating 67 a PKCS12 file""" 68 69 def __init__(self, paramStream): 70 self.cert = pycert.Certificate(paramStream) 71 self.key = pykey.keyFromSpecification("default") 72 73 def toDER(self): 74 with NamedTemporaryFile(mode="wt+") as certTmp, NamedTemporaryFile( 75 mode="wt+" 76 ) as keyTmp, NamedTemporaryFile(mode="rb+") as pkcs12Tmp: 77 certTmp.write(self.cert.toPEM()) 78 certTmp.flush() 79 keyTmp.write(self.key.toPEM()) 80 keyTmp.flush() 81 openssl = shutil.which("openssl") 82 status = runUtil( 83 openssl, 84 [ 85 "pkcs12", 86 "-export", 87 "-inkey", 88 keyTmp.name, 89 "-in", 90 certTmp.name, 91 "-out", 92 pkcs12Tmp.name, 93 "-passout", 94 "pass:password", 95 ], 96 ) 97 if status != 0: 98 raise OpenSSLError(status) 99 return pkcs12Tmp.read() 100 101 def toPEM(self): 102 output = "-----BEGIN PKCS12-----" 103 der = self.toDER() 104 b64 = base64.b64encode(der).decode() 105 while b64: 106 output += "\n" + b64[:64] 107 b64 = b64[64:] 108 output += "\n-----END PKCS12-----" 109 return output 110 111 112 # The build harness will call this function with an output 113 # file-like object and a path to a file containing a 114 # specification. This will read the specification and output 115 # the PKCS12 file. 116 def main(output, inputPath): 117 with open(inputPath) as configStream: 118 output.write(PKCS12(configStream).toDER()) 119 120 121 # When run as a standalone program, this will read a specification from 122 # stdin and output the PKCS12 file as PEM to stdout. 123 if __name__ == "__main__": 124 print(PKCS12(sys.stdin).toPEM())