Last active
February 19, 2017 21:10
-
-
Save cwells/e87b362b34fb697d34fcf22f20baa80b to your computer and use it in GitHub Desktop.
This file contains hidden or 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 | |
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