OpenSSL python library extends all the functions of OpenSSL into python, such as creation and verification of CSR/Certificates. In this post, we present a simple utility in python to Create CSR & Self Signed Certificates in commonly used key formats namely PEM, DER, PFX or P12.

We will have this built in such a way that all the configurations needed to generate CSR/Keys/Cert can be configured in a yaml template (Config.yaml). The path to yaml template can be provided as an argument at the time of instantiation, as in the following example.

fork-git

Usage

Files

├── Config.yaml
├── Gen_CA.py
└── test.py

Gen_CA.py (OpenSSL wrapper)

from OpenSSL import crypto
import os
import re
import yaml
from OpenSSL.crypto import TYPE_RSA, TYPE_DSA, FILETYPE_PEM, load_certificate_request, PKCS12, FILETYPE_ASN1, load_privatekey, X509Req


class Secure: 
    def __init__(self, ConfigFile):
        self.ConfigFile = ConfigFile
        CertConfig = self.ParseConfig()
        if CertConfig['CERT'].get('KeyType') == 'RSA':
            self.KeyType = TYPE_RSA
        elif CertConfig['CERT'].get('KeyType') == 'DSA':
            self.KeyType = TYPE_DSA
        self.CsrFile = CertConfig['CSR'].get('OldCsrFile')
        self.OldCsrFileType = CertConfig['CSR'].get('OldCsrFileType')
        self.BitLength = CertConfig['CERT'].get('BitLength')
        self.digestType = CertConfig['CERT'].get('digestType')
        self.CertDir = CertConfig['CERT'].get('CertDir')
        self.set_notBefore = CertConfig['CSR'].get('validfrom')
        self.set_notAfter = CertConfig['CSR'].get('validto')
        self.OldPrivateKey = CertConfig['REUSE'].get('OldPrivateKey')
        self.OldPrivateKeyType = CertConfig['REUSE'].get('OldPrivateKeyType')
        self.Certificate = CertConfig['REUSE'].get('Certificate')
        self.validfrom = CertConfig['CERT'].get('validfrom')
        self.validto = CertConfig['CERT'].get('validto')
        

    def ParseConfig(self):
        with open(self.ConfigFile) as Config:
            try:
                CertConfig = yaml.safe_load(Config)
            except Exception as ConfigException:
                print("Failed to read Configuration %s" %(ConfigException))
        return CertConfig
      
    def GetPrivateKey(self):
        if not self.OldPrivateKey:
            Key = crypto.PKey()
            Key.generate_key(self.KeyType, self.BitLength)
        else:
            with open(self.OldPrivateKey) as KeyFile:
                if self.OldPrivateKeyType == 'PEM':
                    Key = load_privatekey(FILETYPE_PEM, KeyFile.read())
                elif self.OldPrivateKeyType == 'DER':
                    Key = load_privatekey(FILETYPE_ASN1, KeyFile.read())
        return Key
                
    def CreateCsr(self, Key):
        if not self.CsrFile:
            CertConfig = self.ParseConfig()
            Csr = X509Req()
            Csr.get_subject().commonName = CertConfig['CSR'].get('commonName')
            Csr.get_subject().stateOrProvinceName = CertConfig['CSR'].get('stateOrProvinceName')
            Csr.get_subject().localityName = CertConfig['CSR'].get('localityName')
            Csr.get_subject().organizationName = CertConfig['CSR'].get('organizationName')
            Csr.get_subject().organizationalUnitName = CertConfig['CSR'].get('organizationalUnitName')
            Csr.get_subject().emailAddress = CertConfig['CSR'].get('emailAddress')
            Csr.get_subject().countryName = CertConfig['CSR'].get('countryName')
            Csr.set_pubkey(Key)
            Csr.sign(Key, self.digestType)
        else:
            with open(self.CsrFile) as CsrFile:
                if self.OldCsrFileType == 'PEM':
                    Csr = load_certificate_request(FILETYPE_PEM, CsrFile.read())
                elif self.OldCsrFileType == 'DER':
                    Csr = load_certificate_request(FILETYPE_ASN1, CsrFile.read())
                else:
                    raise TypeError("Unknown Certificate Type %s" %(self.OldCsrFileType))
        return Csr
    
    def CreateCert(self, Csr, Key):
        Cert = crypto.X509()
        Cert.get_subject().commonName = Csr.get_subject().commonName
        Cert.get_subject().stateOrProvinceName = Csr.get_subject().stateOrProvinceName
        Cert.get_subject().localityName = Csr.get_subject().localityName
        Cert.get_subject().organizationName = Csr.get_subject().organizationName
        Cert.get_subject().organizationalUnitName = Csr.get_subject().organizationalUnitName
        Cert.get_subject().emailAddress = Csr.get_subject().emailAddress
        Cert.get_subject().countryName = Csr.get_subject().countryName
        if self.validfrom and self.validto:
            Cert.set_notBefore(self.validfrom)
            Cert.set_notAfter(self.validto)
        Cert.set_pubkey(Key)
        Cert.sign(Key, self.digestType)
        return Cert
    
    def CreateP12(self, Key, Cert, p12File, passphrase=None):
        p12 = PKCS12()
        p12.set_certificate(Cert)
        p12.set_privatekey(Key)
        p12File.write(p12.export(passphrase = passphrase))
        
    
    def DumpKeyCertCsr(self):
        Key = self.GetPrivateKey()
        Csr = self.CreateCsr(Key)
        Cert = self.CreateCert(Csr, Key)
        cn = Csr.get_subject().commonName
        cn = re.sub(' +', '_', cn)
        
        #Files in PEM format
        PemKeyPath = os.path.join(self.CertDir, cn + "_pkey.pem")
        PemCsrPath = os.path.join(self.CertDir, cn + "_csr.pem")
        PemCertPath = os.path.join(self.CertDir, cn + "_cert.pem")
        if not self.OldPrivateKey:
            with open(PemKeyPath, "w") as fPemPrivKey:
                fPemPrivKey.write(crypto.dump_privatekey(FILETYPE_PEM, Key))
        if not self.CsrFile:
            with open(PemCsrPath, "w") as fPemcsr:
                fPemcsr.write(crypto.dump_certificate_request(FILETYPE_PEM, Csr))
        with open(PemCertPath, "w") as fPemcert:
            fPemcert.write(crypto.dump_certificate(FILETYPE_PEM, Cert))
            
        #Files in DER (a.k.a ASN1) format
        DerKeyPath = os.path.join(self.CertDir, cn + "_pkey.der")
        DerCsrPath = os.path.join(self.CertDir, cn + "_csr.der")
        DerCertPath = os.path.join(self.CertDir, cn + "_cert.der")
        if not self.OldPrivateKey:
            with open(DerKeyPath, "w") as fDerPrivKey:
                fDerPrivKey.write(crypto.dump_privatekey(FILETYPE_ASN1, Key))
        if not self.CsrFile:                
            with open(DerCsrPath, "w") as fDercsr:
                fDercsr.write(crypto.dump_certificate_request(FILETYPE_ASN1, Csr))
        with open(DerCertPath, "w") as fDercert:
            fDercert.write(crypto.dump_certificate(FILETYPE_ASN1, Cert))
            
        #Files in p12/PFX format
        P12CertPath = os.path.join(self.CertDir, cn + "_cert.p12")
        fP12cert = open(P12CertPath, "w")
        self.CreateP12(Key, Cert, fP12cert)

Certificate Configuration

Config.yaml, sample configuration.

---
# # # # # # # # # # # # # # # # # # # # # # # # #
# This file is used to specify configurations   #
# for creating/renewing Certificates.           #
# Always specify full path wherever applicable. #
# # # # # # # # # # # # # # # # # # # # # # # # #

# CSR Configuration
# CSR configurations will be ignored, when an 
# existing CSR path is provided in 'OldCsrFile'

CSR:
   commonName: "unixutils"                            # Mandatory if 'OldCsrFile' is not used
   stateOrProvinceName: "MyState"                     # Mandatory if 'OldCsrFile' is not used
   localityName: "MyLocality "                        # Mandatory if 'OldCsrFile' is not used
   organizationName: "unixutils"                      # Mandatory if 'OldCsrFile' is not used
   organizationalUnitName: "Certification Authority"  # Mandatory if 'OldCsrFile' is not used
   emailAddress: "admin@unixutils.com"                # Mandatory if 'OldCsrFile' is not used
   countryName: "IN"                                  # Mandatory if 'OldCsrFile' is not used
   OldCsrFile:                                        # Optional
   OldCsrFileType:                                    # Mandatory IF 'OldCsrFile' is used, example: "PEM"

# CERT Configuration   
# The followin configurations are applied on
# the generated certificate.
# validty: Date Format: YYYYMMDDhhmmssZ (suffixed with 'Z')

CERT:
    KeyType: RSA                                      # Mandatory
    BitLength: 2048                                   # Mandatory
    digestType: 'sha256'                              # Mandatory
    CertDir: '/home/admin/mysslcerts'                 # Mandatory
    validfrom: "20200101000000Z"                      # Mandatory
    validto: "20210101000000Z"                        # Mandatory

# CREATE CERTIFICATE WITH OLD PRIVATE KEY

REUSE:
    OldPrivateKey:                                    # Optional 
    OldPrivateKeyType:                                # Mandatory IF OldPrivateKey is used, example: "PEM"

Test script

Generate certificates from Configuration.

from Gen_CA import Secure

x = Secure('Config.yaml')
x.DumpKeyCertCsr()

With this being run, you should be able to see the CSR, Private Key and Certificate in the intended formats under the path defined as ‘CertDir’ in Config.yaml