Created
April 5, 2020 02:40
-
-
Save jrmdev/9b57cbdf3ab0b7e53765ddcee68867f9 to your computer and use it in GitHub Desktop.
This is a helper script I made to assist with common OpenSSL command lines. See help for available commands.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import os | |
import sys | |
from subprocess import CalledProcessError, check_output, PIPE | |
class ShellCmd(): | |
def __init__(self, cmd): | |
self.output = None | |
self.returncode = None | |
self.command = cmd | |
self.run() | |
def run(self): | |
print self.command | |
try: | |
self.output = check_output(self.command, stderr=PIPE) | |
self.returncode = 0 | |
except CalledProcessError as e: | |
self.output = e.output | |
self.returncode = e.returncode | |
class OSSLW(): | |
"""Main openssl-wrapper class""" | |
def __init__(self, file=None): | |
# Certificate subject parameters | |
self.country = 'AU' | |
self.state = 'Victoria' | |
self.location = 'Melbourne' | |
self.orga = 'ACME Corp, Inc.' | |
self.unit = 'IT Dept' | |
self.common_name = '*.acmecorp.com' | |
self.nb_days = 365 | |
self.key_bits = 2048 | |
# Input file parameters | |
self.file_path = file | |
self.file_content = None | |
self.file_format = None | |
self.file_type = None | |
self.file_desc = None | |
self.file_dump = None | |
# General parameters | |
self.openssl_path = 'openssl' | |
self.pass_in = 'password' | |
self.pass_out = 'password' | |
if self.file_path: | |
self.file_info() | |
def set_file(self, file): | |
self.file_path = file | |
self.file_info() | |
def set_passin(self, passwd): | |
self.pass_in = passwd | |
def set_passout(self, passwd): | |
self.pass_out = passwd | |
def file_info(self): | |
if not os.path.exists(self.file_path): | |
self.error("%s: no such file" % self.file_path) | |
with open(self.file_path, 'rb') as f: | |
self.file_content = f.read() | |
if '-----BEGIN' in self.file_content: | |
self.file_format = 'pem' | |
elif self.file_content.startswith('\x30\x82'): | |
self.file_format = 'der' | |
else: | |
self.error("%s: file is neither in PEM or DER format." % self.file_path) | |
commands = [ | |
('x509', 'certificate', [self.openssl_path, 'x509', '-inform', self.file_format, '-in', self.file_path, '-text', '-noout']), | |
('req', 'Certificate Signing Request (CSR)', [self.openssl_path, 'req', '-inform', self.file_format, '-in', self.file_path, '-text', '-noout']), | |
('rsa', 'RSA Public Key', [self.openssl_path, 'rsa', '-inform', self.file_format, '-pubin', '-in', self.file_path, '-text', '-noout']), | |
('rsa', 'RSA Private Key', [self.openssl_path, 'rsa', '-inform', self.file_format, '-in', self.file_path, '-text', '-noout']), | |
('pkcs12', 'PKCS#12 Bundle', [self.openssl_path, 'pkcs12', '-info', '-in', self.file_path, '-passin', 'pass:%s' % self.pass_in, '-passout', 'pass:%s' % self.pass_out]), | |
] | |
for command in commands: | |
cmd = ShellCmd(command[2]) | |
if not cmd.returncode: | |
self.file_type = command[0] | |
self.file_desc = command[1] | |
self.file_dump = cmd.output | |
break | |
if not self.file_type: | |
self.error("File '%s' is neither of a certificate, key, CSR or bundle. (wrong password?)" % self.file_path) | |
def error(self, msg): | |
sys.exit("[!] %s" % msg) | |
def check(self): | |
"""Display basic information about the file""" | |
print self.file_dump | |
print "[+] File '%s' is a %s in the %s format." % (self.file_path, self.file_desc, self.file_format.upper()) | |
def dump(self): | |
"""Dump parameters of the RSA key for reuse in a script""" | |
print "# File '%s' is a %s." % (self.file_path, self.file_desc) | |
if self.file_desc == 'RSA Public Key': | |
command = ShellCmd([self.openssl_path, 'rsa', '-pubin', '-noout', '-text', '-in', self.file_path]) | |
elif self.file_desc == 'RSA Private Key': | |
command = ShellCmd([self.openssl_path, 'rsa', '-noout', '-text', '-in', self.file_path]) | |
else: | |
self.error("Only RSA keys can be dumped.") | |
fields = {'modulus': 'N', 'Modulus': 'N', 'Exponent': 'e', 'publicExponent': 'e', 'privateExponent': 'd', 'prime1': 'p', 'prime2': 'q', 'exponent1': 'dp', 'exponent2': 'dq', 'coefficient': 'coeff'} | |
output = command.output.strip() | |
output = output.replace(':\n ', ':').split('\n') | |
for i in range(1, len(output)): | |
line = output[i].split(':', 1) | |
try: | |
val = line[1].replace(':','') | |
val = int(val, 16) | |
except: | |
val = line[1].strip().split() | |
val = int(val[0]) | |
print '%s = %d' % (fields[line[0]], val) | |
def der2pem(self): | |
"""Convert a DER file into PEM format""" | |
if self.file_format == 'pem': | |
self.error("File %s already in PEM format" % self.file_path) | |
outfile = os.path.splitext(self.file_path)[0] + '.pem' | |
cmd = ShellCmd([self.openssl_path, self.file_type, '-inform', 'der', '-in', self.file_path, '-outform', 'pem', '-out', outfile]) | |
if not cmd.returncode: | |
print "[i] Saving PEM file as %s" % outfile | |
else: | |
self.error("[i] Format not supported for conversion.") | |
def pem2der(self): | |
"""Convert a PEM file into DER format""" | |
if self.file_format == 'der': | |
self.error("File %s already in DER format" % self.file_path) | |
outfile = os.path.splitext(self.file_path)[0] + '.cer' | |
cmd = ShellCmd([self.openssl_path, self.file_type, '-inform', 'pem', '-in', self.file_path, '-outform', 'der', '-out', outfile]) | |
if not cmd.returncode: | |
print "[i] Saving DER file as %s" % outfile | |
else: | |
self.error("[i] Format not supported for conversion.") | |
def get_pubkey(self): | |
"""Derive the public key from a private key""" | |
outfile = os.path.splitext(self.file_path)[0] + '.pub' | |
command = ShellCmd([self.openssl_path, 'rsa', '-in', self.file_path, '-pubout', '-out', outfile]) | |
if command.returncode: | |
self.error("An error occurred.") | |
else: | |
print "[+] Saving %s" % outfile | |
def key_unprotect(self): | |
"""Remove the password from a private key file""" | |
outfile = os.path.splitext(self.file_path)[0] + '_nopass.key' | |
command = ShellCmd([self.openssl_path, 'rsa', '-in', self.file_path, '-out', outfile, '-passin', 'pass:%s' % self.pass_in]) | |
if command.returncode: | |
self.error("An error occurred.") | |
else: | |
print "[+] Saving %s" % outfile | |
def gen_selfsigned(self, basename): | |
"""Create a self-signed certificate and key""" | |
base = os.path.basename(basename) | |
subj = "/C=%s/ST=%s/L=%s/O=%s/OU=%s/CN=%s" % (self.country, self.state, self.location, self.orga, self.unit, self.common_name) | |
command = ShellCmd([self.openssl_path, 'req', '-x509', '-sha256', '-nodes', '-days', str(self.nb_days), '-newkey', 'rsa:%d' % self.key_bits, '-keyout', '%s.key' % base, '-outform', 'pem', '-out', '%s.pem' % base, '-subj', subj]) | |
if command.returncode: | |
self.error("An error occurred.") | |
command = ShellCmd([self.openssl_path, 'rsa', '-in', '%s.key' % base, '-pubout', '-out', '%s.pub' % base]) | |
if command.returncode: | |
self.error("An error occurred.") | |
print "Files:" | |
print " [+] Certificate : %s.pem" % base | |
print " [+] Private key : %s.key" % base | |
print " [+] Public key : %s.pub" % base | |
def ca_create(self): | |
"""Create a self-signed CA certificate and key""" | |
self.common_name = 'Certification Authority for %s' % self.common_name | |
self.gen_selfsigned('rootCA') | |
def ca_sign(self, basename, cacert, cakey, privkey=None): | |
"""Create a CSR from the provided key, then create a CA-signed certificate using the provided CA-cert and CA-key and the CSR generated.""" | |
base = os.path.basename(basename) | |
cacert = OSSLW(cacert) | |
cakey = OSSLW(cakey) | |
if not privkey: | |
cmd = ShellCmd([self.openssl_path, 'genrsa', '-out', '%s.key' % base, str(self.key_bits)]) | |
privkey = OSSLW('%s.key' % base) | |
else: | |
privkey = OSSLW(privkey) | |
subj = "/C=%s/ST=%s/L=%s/O=%s/OU=%s/CN=%s" % (self.country, self.state, self.location, self.orga, self.unit, self.common_name) | |
commands = ( | |
[self.openssl_path, 'req', '-new', '-key', privkey.file_path, '-out', '%s.csr' % base, '-subj', subj], | |
[self.openssl_path, 'x509', '-req', '-in', '%s.csr' % base, '-CA', cacert.file_path, '-CAkey', cakey.file_path, '-CAcreateserial', '-out', '%s.pem' % base, '-days', str(self.nb_days), '-sha256'] | |
) | |
for command in commands: | |
c = ShellCmd(command) | |
if c.returncode: | |
self.error("An error occurred.") | |
print "Files:" | |
print " [i] Using Private Key : %s" % privkey.file_path | |
print " [i] Using CA Certificate : %s" % cacert.file_path | |
print " [i] Using CA key : %s" % cakey.file_path | |
print " ---" | |
print " [+] Server Key : %s" % privkey.file_path | |
print " [+] Server Cert : %s.pem" % base | |
print " [+] Server CSR : %s.csr" % base | |
def pfx_unprotect(self): | |
outfile = os.path.splitext(self.file_path)[0] + '_nopass.pfx' | |
command = ShellCmd([self.openssl_path, 'pkcs12', '-in', self.file_path, '-nodes', '-out', 'temp.pem', '-passin', 'pass:%s' % self.pass_in]) | |
if command.returncode: | |
self.error("An error occurred.") | |
command = ShellCmd([self.openssl_path, 'pkcs12', '-export', '-in', 'temp.pem', '-out', outfile, '-password', 'pass:%s' % self.pass_out]) | |
if command.returncode: | |
self.error("An error occurred.") | |
print "[+] Saved as %s." % outfile | |
os.unlink('temp.pem') | |
def pfx_pack(self, cert, key, cacert=None): | |
outfile = os.path.splitext(cert)[0] + '.pfx' | |
cert = OSSLW(cert) | |
key = OSSLW(key) | |
if cacert: | |
cacert = OSSLW(cacert) | |
command = ShellCmd([self.openssl_path, 'pkcs12', '-export', '-out', outfile, '-certfile', cacert.file_path, '-inkey', key.file_path, '-in', cert.file_path, '-passout', 'pass:%s' % self.pass_out]) | |
else: | |
command = ShellCmd([self.openssl_path, 'pkcs12', '-export', '-out', outfile, '-inkey', key.file_path, '-in', cert.file_path, '-passout', 'pass:%s' % self.pass_out]) | |
if command.returncode: | |
self.error("An error occurred.") | |
print "[+] Saved as %s" % outfile | |
def pfx_unpack(self): | |
outfile = os.path.splitext(self.file_path)[0] | |
# Exract cert from pfx | |
command = ShellCmd([self.openssl_path, 'pkcs12', '-in', self.file_path, '-passin', 'pass:%s' % self.pass_in, '-out', '%s.pem' % outfile, '-nodes', '-nokeys']) | |
if command.returncode: | |
self.error("An error occurred.") | |
# Exract priv key from pfx | |
command = ShellCmd([self.openssl_path, 'pkcs12', '-in', self.file_path, '-passin', 'pass:%s' % self.pass_in, '-out', '%s.key' % outfile, '-nodes', '-nocerts']) | |
if command.returncode: | |
self.error("An error occurred.") | |
# Exract pub key from priv key | |
command = ShellCmd([self.openssl_path, 'rsa', '-in', '%s.key' % outfile, '-pubout', '-out', '%s.pub' % outfile]) | |
if command.returncode: | |
self.error("An error occurred.") | |
print "Files:" | |
print " [+] Certificate : %s.pem" % outfile | |
print " [+] Private key : %s.key" % outfile | |
print " [+] Public key : %s.pub" % outfile | |
def main(): | |
cmd = sys.argv[1] | |
osslw = OSSLW() | |
if len(sys.argv) > 3: | |
sys.argv.append("") | |
if cmd == 'check' and len(sys.argv) >= 3: | |
if len(sys.argv) > 3: | |
osslw.set_passin(sys.argv[3]) | |
osslw.set_passout(sys.argv[4]) | |
osslw.set_file(sys.argv[2]) | |
osslw.check() | |
elif cmd == 'dump' and len(sys.argv) >= 3: | |
if len(sys.argv) > 3: | |
osslw.set_passin(sys.argv[3]) | |
osslw.set_passout(sys.argv[4]) | |
osslw.set_file(sys.argv[2]) | |
osslw.dump() | |
elif cmd == 'der2pem' and len(sys.argv) == 3: | |
osslw.set_file(sys.argv[2]) | |
osslw.der2pem() | |
elif cmd == 'pem2der' and len(sys.argv) == 3: | |
osslw.set_file(sys.argv[2]) | |
osslw.pem2der() | |
elif cmd == 'key_extract' and len(sys.argv) == 3: | |
osslw.set_file(sys.argv[2]) | |
osslw.get_pubkey() | |
elif cmd == 'key_unprotect' and len(sys.argv) >= 3: | |
if len(sys.argv) > 3: | |
osslw.set_passin(sys.argv[3]) | |
osslw.set_passout(sys.argv[4]) | |
osslw.set_file(sys.argv[2]) | |
osslw.key_unprotect() | |
elif cmd == 'pfx_unprotect' and len(sys.argv) >= 3: | |
if len(sys.argv) > 3: | |
osslw.set_passin(sys.argv[3]) | |
osslw.set_passout(sys.argv[4]) | |
osslw.set_file(sys.argv[2]) | |
osslw.pfx_unprotect() | |
elif cmd == 'pfx_pack' and len(sys.argv) >= 4: | |
osslw.pfx_pack(*sys.argv[2:]) | |
elif cmd == 'pfx_unpack' and len(sys.argv) >= 3: | |
if len(sys.argv) > 3: | |
osslw.set_passin(sys.argv[3]) | |
osslw.set_passout(sys.argv[3]) | |
osslw.set_file(sys.argv[2]) | |
osslw.pfx_unpack() | |
elif cmd == 'cert_create' and len(sys.argv) == 3: | |
osslw.gen_selfsigned(sys.argv[2]) | |
elif cmd == 'ca_create' and len(sys.argv) == 2: | |
osslw.ca_create() | |
elif cmd == 'ca_sign' and len(sys.argv) >= 5: | |
osslw.ca_sign(*sys.argv[2:]) | |
else: | |
usage() | |
def usage(): | |
print "output files extensions:" | |
print ".der, .cer (DER-encoded certificates or keys)" | |
print ".pem, .crt (PEM-encoded certificates or keys)" | |
print ".pfx, .p12 (PKCS#12 bundles)" | |
print ".key (RSA private keys)" | |
print ".pub (RSA public keys)" | |
print ".csr (RSA certificate signing requests)" | |
print "usage: %s <cmd>" % sys.argv[0] | |
print "" | |
print " check <file> [ passwd ] -- display information about the file" | |
print " dump <file> [ passwd ] -- dump parameters from an RSA key" | |
print " der2pem <file> -- convert a cert or key to PEM format" | |
print " pem2der <file> -- convert a cert or key to DER format" | |
print " cert_create <basename> -- generate a self-signed certificate and key" | |
print " key_extract <file> -- derive the public key from a private key" | |
print " key_unprotect <file> <passwd> -- remove the password from a private key file" | |
print " pfx_unprotect <file> <passwd> -- remove the password from a PKCS12 bundle" | |
print " pfx_pack <cert> <key> [ cacert ] -- compress cert(s) and priv. key into PKCS12" | |
print " pfx_unpack <file> [ passwd ] -- extract PKCS12 into cert(s) and priv. key" | |
print " ca_create -- create a CA certificate and key" | |
print " ca_sign <basename> <cacert> <cakey> [ privkey ] -- generate a CA-signed certificate using an existing CA." | |
print " If a private key isn't provided, one will be generated." | |
sys.exit(1) | |
if __name__ == '__main__': | |
usage() if len(sys.argv) < 2 else main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment