Skip to content

Instantly share code, notes, and snippets.

@cwells
Last active February 19, 2017 21:10
Show Gist options
  • Save cwells/e87b362b34fb697d34fcf22f20baa80b to your computer and use it in GitHub Desktop.
Save cwells/e87b362b34fb697d34fcf22f20baa80b to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
from OpenSSL import crypto
import click
def validate_country(ctx, param, value):
try:
assert len(value) == 2
except AssertionError:
raise click.BadParameter('{} must be two letters'.format(param.name))
else:
return value.upper()
def validate_org(ctx, param, value):
try:
assert len(set(value).intersection(set("<>~!@#$%^*/\\()?.,&"))) == 0
except AssertionError:
raise click.BadParameter('Invalid characters in {}'.format(param.name))
else:
return value
@click.command()
@click.argument('common_name', type=str, required=True)
@click.argument('subject_alternative_names', type=str, nargs=-1)
@click.option('--private_key', '-k', type=click.File('rb'))
@click.option('--country', '-c', prompt="Country Name (2 letter code)", default='US', callback=validate_country)
@click.option('--province', '-p', prompt="State or Province (full name)", default='Oregon')
@click.option('--locality', '-l', prompt="Locality Name (eg, city)", default='Portland')
@click.option('--organization', '-o', prompt="Organization Name (eg, company)", default='FocusVision', callback=validate_org)
@click.option('--organization_unit', '-u', prompt="Organizational Unit (eg, section)", default='SRE', callback=validate_org)
def main(common_name, subject_alternative_names, private_key, country, province, locality, organization, organization_unit):
'''Generates a CSR using the provided private key. If no private key is provided,
a new one is generated and used to sign the CSR. The resulting CSR and key are
written to files named after the COMMON_NAME.
If country, province, locality, organization, and organization_unit are not provided,
they will be prompted for.
'''
common_name_encoded = common_name.replace('*', 'STAR').replace('.', '_')
csrfile = '{}.csr'.format(common_name_encoded)
keyfile = '{}.key'.format(common_name_encoded)
san = ", ".join([ "DNS: {}".format(s) for s in subject_alternative_names ])
req = crypto.X509Req()
req.get_subject().CN = common_name
req.get_subject().countryName = country
req.get_subject().stateOrProvinceName = province
req.get_subject().localityName = locality
req.get_subject().organizationName = organization
req.get_subject().organizationalUnitName = organization_unit
x509_extensions = [
crypto.X509Extension("keyUsage", False, "Digital Signature, Non Repudiation, Key Encipherment"),
crypto.X509Extension("basicConstraints", False, "CA:FALSE"),
]
if san:
x509_extensions.append(crypto.X509Extension("subjectAltName", False, san))
req.add_extensions(x509_extensions)
if private_key:
key = crypto.load_privatekey(crypto.FILETYPE_PEM, private_key.read())
else:
key = crypto.PKey()
key.generate_key(crypto.TYPE_RSA, 2048)
req.set_pubkey(key)
req.sign(key, "sha256")
open(csrfile, 'wb').write(crypto.dump_certificate_request(crypto.FILETYPE_PEM, req))
if not private_key:
open(keyfile, 'wb').write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key))
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment