Home » Linux » OpenSSL » PKI – moving from SHA1 to SHA256

It feels like yesterday, deploying the all new multitier internal PKI infra with all the bells and whistles like offline root CA, OCSP, NDES etc. It was the realization of prescribed best practices like Linux based Offline root CA, disk encryption, SHA1 signature to take care of the collision attack with MD5 and the 4K private key, encouraged to go for a 20-year root CA. Infect intermittent changes like OS upgrade of the AD integrated intermediate CA servers as well as key renewal were all going smooth, giving the impression that we may never have to touch the root CA, at least in the initial 10-year. But here we are with the SHA1 use end date. Good part with PKI, as long as the private key is fine, changes like these are not a big problem. However, it must be tested thoroughly as otherwise it can bring catastrophe in accessing enterprise systems and application. In this post, I am going to leverage OpenSSL, the all-inclusive cryptographic tool, to simulate, test and verify this migration from SHA1 to SHA256. Yes, I can go beyond SHA256 but all the clients should have support for that, SHA256 scores high now in that regard.

Let me first create a test CA structure using SHA1 digest sign the OBJECT field i.e. SHA1 signature. I am using a new folder /var/pkpnotes inside this Linux VM for these activities. It could be anything, say e:\pkpnotes if using OpenSSL on windows OS.

mkdir certs private newcerts crl etc  è this would create the required folder structure

echo 1000 > serial

touch index

While inside the folder /var/pkpnotes, I am going to create CA private key of 4096 bit size
openssl  genrsa -aes256 -out  ./private/rootca-key.pem 4096

Please remember the password entered above!!! Yes, I am using the Ubuntu bash on Windows 10 and loving the native way of interacting with Linux systems from this Linux subsystem. J

In the step above I created a RSA private key for the CA which essentially means generation of two prime numbers. Security of the CA is as good as of its private key and hence the private key is further encrypted using AES256 algorithm and the pass phrase entered above. cat private/rootca-key.pem would show the private key with some random text

Details of RSA private key like prime numbers, exponets etc. can be seen with OpenSSL rsa -in private/rootca-key.pem -text . The Public key can be exported from this key file with OpenSSL rsa -in private/rootca-key.pem -pubout.

I am good to generate the X509 CA certificate from the private key created above. Just that a OpenSSL.cnf configuration file is needed to specify the policies and defaults for CA operation of OpenSSL. Here is the format of the etc/OpenSSL.cnf file that I am using as per the reference of OpenSSL documentation at https://www.OpenSSL.org/docs/manmaster/apps/config.html

# $Id: OpenSSL.cnf,v 1.1 19/10/2016 PkpNotes demo Exp $
#
# OpenSSL configuration file for PKPnotes Root Certificate Authority. 
# This one is for use only in Certificate Authority operations (csr ->
# cert, cert revocation, revocation list generation).
#

HOME                    = .
RANDFILE                = /dev/random

[ ca ]
default_ca      = CA_default

[ CA_default ]
dir             = /var/rootca
certs           = $dir/certs
new_certs_dir   = $dir/newcerts
crl_dir         = $dir/crl
database        = $dir/index
certificate     = $dir/certs/rootca-cert.pem
serial          = $dir/serial
crl             = $dir/rootca-crl.pem
private_key     = $dir/private/rootca-key.pem
RANDFILE        = $dir/private/.rand

x509_extensions = usr_cert

# Comment out the following two lines for the "traditional"
# (and highly broken) format.
name_opt        = ca_default
cert_opt        = ca_default

default_days    = 5500                  # how long to certify for
default_crl_days= 90                    # how long before next CRL
default_md      = sha1                  # which md to use.

# MSIE may need following set to yes?
preserve        = no

# A few difference way of specifying how similar the request should look
# For type CA, the listed attributes must be the same, and the optional
# and supplied fields are just that :-)
policy          = policy_match

# For the CA policy
[ policy_match ]
countryName             = match
stateOrProvinceName     = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

# For the 'anything' policy
# At this point in time, you must list all acceptable 'object'
# types.
[ policy_anything ]
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

####################################################################
[ req ]
default_bits            = 4096
default_keyfile         = ./private/rootca-key.pem
default_md              = sha1

prompt                  = no
distinguished_name      = root_ca_distinguished_name

x509_extensions = v3_ca

# Passwords for private keys if not present they will be prompted for
# input_password = secret
# output_password = secret

# This sets a mask for permitted string types. There are several options. 
# default: PrintableString, T61String, BMPString.
# pkix   : PrintableString, BMPString.
# utf8only: only UTF8Strings.
# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings).
# MASK:XXXX a literal mask value.
# WARNING: current versions of Netscape crash on BMPStrings or UTF8Strings
# so use this option with caution!
string_mask = nombstr

# req_extensions = v3_req

[ root_ca_distinguished_name ]
countryName            = IN
0.organizationName     = PKPnotes Ltd
organizationalUnitName = IMG Ent. PKI
commonName             = PKPnotes Root Certificate Authority

[ usr_cert ]

# These extensions are added when 'ca' signs a request.
# This goes against PKIX guidelines but some CAs do it and some software
# requires this to avoid interpreting an end user certificate as a CA.

basicConstraints=CA:FALSE

# PKIX recommendations harmless if included in all certificates.
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer:always

nsCaRevocationUrl               = https://cert.yourdomain.net/ca-crl.pem
#nsBaseUrl
#nsRevocationUrl
#nsRenewalUrl
nsCaPolicyUrl			= https://cert.yourdomain.net/ca_policy.php
#nsSslServerName

[ v3_req ]

# Extensions to add to a certificate request

basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment

[ v3_ca ]

# Extensions for a typical CA
# PKIX recommendation.

subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always
authorityInfoAccess = @aia_sect
crlDistributionPoints=URI:http://pki.PKPnotes.com/crl/rootca.crl
basicConstraints = critical,CA:true
keyUsage = digitalSignature, nonRepudiation, cRLSign, keyCertSign
certificatePolicies=ia5org,@polsect

[ v3_subca ]

# Extensions for a typical CA
# PKIX recommendation.

subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always
basicConstraints = critical,CA:true

[ aia_sect ]
caIssuers;URI=http://pki.PKPnotes.com/pki/rootca.cer

[ polsect ]
policyIdentifier = 1.3.5.8.05092011.05.09.2011
CPS.1="http://pki.PKPnotes.com/pki/rootca_cps.pdf";
userNotice.1=@notice
 [notice]
explicitText="PKPnotes Root Certificate Authority for Internal Use"
organization="PKPnotes Ltd"
noticeNumbers=01

[ crl_ext ]

# CRL extensions.
# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL.

# issuerAltName=issuer:copy
authorityKeyIdentifier=keyid:always,issuer:always

Things like CRL distribution point, AIA information etc. are defined in the form of configuration, in a way its defined in the properties of AD certificate service.

Once the configuration file in place, let me create the X509 CA certificate using SHA1 hash in signature.

openssl req -new -x509 -days 5500 -sha1 -set_serial 01 -key private/rootca-key.pem -extensions v3_ca -out certs/rootca-cert.pem -config etc/OpenSSL.cnf

Yes, replacing the -sha1 with -sha256 would give me the new CA certificate using SHA2 signature. Which is what I am going to do in production based on the outcome of this validation.

Abstract Syntax Notation One (ASN.1) standard and notion is used to structure the X509 certificate. OpenSSL x509 module, that I used above to create the CA certificate, also allows to read the certificate in human readable format. Other option would be to copy the entire content of the ./certs/rootca-cert.pem file, including the beginning and end certificate line, to a notepad on a windows machine and opening after save it as a .cer file.

OpenSSL also has asn1parse module to parse the certificate structure in native format.

I am looking for the signature part which comes towards the end.

Bit string containing the actual signature is at offset 1248, lets me export that to a file

openssl asn1parse -in certs/rootca-cert.pem -out casignsha1 -noout -strparse 1248

To verify the signature, I required the public key. I can export it from private key but instead I will extract from CA certificate as in real world no one except the CA would have the private key.

openssl x509 -in certs/rootca-cert.pem -pubkey -noout > capubkey.pem

Now let me verify the extracted casignsha1 sign file in conjunction with public key, using OpenSSL rsa utility

openssl rsautl -in casignsha1 -verify -asn1parse -inkey capubkey.pem -pubin

So this certificate is good if the SHA1 of the certificate sign part comes as dd:7b:34:c7:33:b7:c1:24:a2:5a:60:58:3b:e1:98:fe:66:94:82:58. I am exporting that part at offset 4 using asn1parse and then take SHA1 sum of the file.

They both match and hence things are working fine now with SHA1 signature. Now I need to repeat the same after putting SHA256 signature in the CA certificate.

openssl req -new -x509 -days 5500 -sha256 -set_serial 01 -key private/rootca-key.pem -extensions v3_ca -out certs/rootca-cert-sha2.pem -config etc/OpenSSL.cnf
openssl asn1parse -in certs/rootca-cert-sha2.pem
openssl asn1parse -in certs/rootca-cert-sha2.pem -out casignsha2 -noout -strparse 1248
openssl rsautl -in casignsha2 -verify -asn1parse -inkey capubkey.pem -pubin
openssl asn1parse -in certs/rootca-cert-sha2.pem -out capartsigned_sha2 -noout -strparse 4

Great, even with SHA256 my validation results are positive.

Here is the side by side comparison of the CA certificate, left hand side in old SHA1 format and right hand side in new SHA256 format. Certificate keys are unchanged and same can be confirmed from having the same public key in both.

At this stage let me check the things at enterprise CAs which got its SubCA certificate in SHA1 format signed by the RootCA with SHA1.

Without making any changed, I just imported the new rootCA singed with SHA256 into the trust root ca certificate store of machine.

If I check the existing certificate of the SubCA i.e. singed with SHA1, seamlessly it is getting mapped to the RootCA signed with SHA256.

Let me now change Hash algorithm of my SubCA to SHA256. This not going to change the Certificate to SHA256 but allow to renew it in SHA256.

certutil -setreg ca\csp\CNGHashAlgorithm SHA256

Certificate service has to restart at this stage.

I am going to renew the certificate now but selecting the option No so that no changes to private-public key pair.

This would generate certificate request file at the root of C-drive. Same I copied to my Linux VM /var/rootca folder to process. First read the request in human readable format to confirm no changes.

openssl req -in certs/subasha2.req -text

openssl ca -config etc/OpenSSL.cnf -in certs/subasha2.req -out certs/subasha2.cer -days 1825 -md sha256 -extensions v3_ca -policy policy_anything

After coping the generated cer file and installing it on the SubCA server using the install certificate task, the certificate now looks SHA256.

OK what if my rootCA is on windows?

For security reason, it’s not recommend to take the CA private key outside. In case the risk are mitigated, CA certificate from the rootCA can be exported and imported into OpenSSL.

CertUtil command line utility has the option -backupkey to backup the CA Key. Likewise, the -restoreKey option allows to import the key. Once the Key is exported, which would be in pfx file format, it can be imported into OpenSSL using pkcs12 module

openssl pkcs12 -in pkpnotes-rootca.p12 -nocerts -out private/winrootca.pem

Once the private key in place, all the steps above can be initiated. Care need to be taken to ensure the match the RootCA name in the opnessl.cnf file matches with that of Windows Root CA. Out of this command would generate the X509 certificate with the subject name as defined in OpenSSL.cnf.

openssl req -new -x509 -days 5500 -sha256 -set_serial 01 -key private/winrootca.pem -extensions v3_ca -out certs/winrootca-cert-sha2.pem -config etc/OpenSSL.cnf

Assuming no differences in CA subject name, I can now pack the certificate and private key into PFX file so that it can be imported on the rootCA windows server using certutil -restorekey

openssl pkcs12 -export -inkey private/winrootca.pem -in certs/winrootca-cert-sha2.pem -CSP “Microsoft Software Key Storage Provider” -out winrootsha2.pfx

2 Replies to “PKI – moving from SHA1 to SHA256”

  1. Vali says:

    Awesome Sir 🙂 Keep it up

  2. pkpanda says:

    Thanks Vali, I am glad that you found this useful

Leave a Reply

Your email address will not be published. Required fields are marked *

*
*