Migrating CA with HSM using Existing CA Mechanism - dogtagpki/pki GitHub Wiki

Table of Contents

Introduction

In this post, I will detail how I migrated a Red Hat Certificate System (RHCS) 8 CA to a RHCS 9.1 (Dogtag 10.3) CA using the existing CA mechanism, using the method in the section described by Installing CA with Existing Certificates using Certificate Files.

Note: Certain HSMs require synchronization (e.g. rfs-sync). Make sure the HSM is synchronized before migrating an existing CA with HSM.

The basics of the migration is as follows:

Existing CA

  • RHEL 5 RHCS 8 (Advanced)
  • Had client software for lunasa installed and configured.
  • Root CA, not a clone.
  • All system keys are in Lunasa HSM.
  • Database is local to the CA.

New CA

  • RHEL 7.2 RHCS 9.1 (Dogtag 10.3 pki-ca-10.3.0.b1-1.el7)
  • Had client software for lunasa installed and configured,
  • Root CA uses the same CA signing certificate and key. The key never leaves the HSM.
  • All other system certificates (ocsp signing cert, subsystem cert, audit signing cert, etc.) are generated anew.
  • All system keys are stored in the same HSM (same nickname for the CA signing cert, new nicknames for the other system certs)
  • Database is local to the CA. Data from the existing CA has been migrated over.

General Steps

  1. Clean up any duplicate signing certificates in the HSM
  2. Retrieve the signing certificate and CSR from the existing CA instance.
  3. Create a new local database on the new CA host.
  4. Construct the correct pkispawn configuration file (using the existing CA parameters)
  5. Run pkispawn to create the new CA instance.
  6. Migrate the data from the old database instance to the new database instance.
  7. Make final adjustments

Specifics

Clean up any duplicate signing certificates in the HSM

  • If the signing certificate has been renewed and reissued using the same keys, multiple certificates could exist in the HSM with the same nickname. This could cause pkispawn to fail to retrieve the correct certificate as reported here: https://bugzilla.redhat.com/show_bug.cgi?id=1424712
  • Before starting the migration, delete any old, duplicate certificate entries in the GSM.

Get the data from the old CA

  • The old CA had an instance name of "pki-rootCA" and therefore was located at /var/lib/pki-rootCA.
  • To export the CA cert and CSR, I used the following commands:
  echo "-----BEGIN NEW CERTIFICATE REQUEST-----" > ca_signing.csr
  sed -n "/^ca.signing.certreq=/ s/^[^=]*=// p" < /var/lib/pki-rootCA/conf/CS.cfg >> ca_signing.csr
  echo "-----END NEW CERTIFICATE REQUEST-----" >> ca_signing.csr
  • If the old CA is an intermediate CA (with a single root CA in the chain), extract the root CA from the certificate database.
  certutil -L -d /var/lib/pki-rootCA/alias -n "root CA nickname" -a > ca_rootca_signing.crt
  • I also backed up the database to an ldif file using the instance specific script generated by setup-ds.pl when the instance was generated. In this case, the database instance name was rootCA (so the directory is /usr/lib64/dirsrv/slapd-rootCA) and the database name was "host1.example.com-pki-rootCA". You can get the database name by checking the value of "internaldb.database" in the CA's CS.cfg.
  cd /usr/lib64/dirsrv/slapd-rootCA
  ./db2ldif -n "host1.example.com-pki-rootCA" -a /tmp/old_ca.ldif
  • The db2ldif command runs as the DB user, so it needs to write to somewhere it has permissions.
  • All of the above (old_ca.ldif, ca_signing.crt, ca_signing.csr) were transferred to the new CA machine. If this is an intermediate CA, also transfer the root CA certificate (ca_rootca_signing.crt)

Run pkispawn on the new CA

  • On the new CA, I created a new database instance using setup-ds.pl. This instance runs on the standard port (389).
  • I created a pkispawn file as below:
 [CA]
 pki_ca_signing_csr_path=/root/transfer_data/ca_signing.csr
 pki_ca_signing_cert_path=/root/transfer_data/ca_signing.crt
 pki_ca_signing_nickname=caSigningCert cert-pki-rootCA
 pki_ca_signing_token=par2
 pki_ds_base_dn=dc=host1.example.com-pki-rootCA
 pki_ds_database=host1.example.com-pki-rootCA
 pki_serial_number_range_start=20
 pki_request_number_range_start=30
 pki_master_crl_enable=False
 pki_cert_chain_path=/root/transfer_data/rootca_signing.crt
 pki_cert_chain_nickname=caSigningCert cert-top-rootca
  • Some notes about the above config:
    • The instance name is set as something that is different from the old CA instance name. It can be the same, but then you need to make sure to define the nicknames for all the system certificates so that there are no conflicts in the HSM with the old system certificates. In practice, you'll probably want to define all the nicknames anyway. The CA signing cert nickname is, of course, set to be exactly the same as the old CA.
    • The pki_hsm_* and pki_token_* parameters are the ones needed to enable and communicate with the Lunasa.
    • pki_existing = True means that we will be using the existing CA mechanism.
    • The pki_ca_signing_* parameters point to the files we copied over from the old machine, and refer to the cert/key of the signing certificate in the HSM. The nickname must be exactly right or the installer will not be able to find the signing key.
    • The pki_ds_base_dn parameter must be the same as the old CA. This is so that we can easily import the old data. You can find this value in the old CA's CS.cfg as "internaldb.basedn".
    • I chose to make the pki_ds_database the same value as in the old CA. Thats probably not necessary, but I saw no reason to change it.
    • The serial number parameters are critical. We want to select the start of the serial and request numbers such that there are no duplicated numbers once the old data is imported. To see which serial numbers to use, I looked at the old CA's agent interface and listed both certificates and request numbers. Assuming the serial numbers are not being assigned randomly, you want a value that is larger than the last number that was issued. For safety sake, for this experiment, I left a bit of a gap between the last issued and the start range. Not that the value for the pki_serial_number_range_start (which is the cert serial number starting number) is in hex - and should be specified without the '0x' prefix. So, the value 20 in the example above is 0x20. The request number (pki_request_number_range_start) is in decimal - so 30 in the above example is in fact equal to 30.
    • pki_master_crl_enable=False is used to prevent the initial creation and publishing of a CRL during the pkispawn process. Instead, the CRL will be imported from the old data in the database migration step below.
    • pki_cert_chain_path and pki_cert_chain_nickname are required if the old CA is an intermediate CA only. In this case, they correspond to the path to the root CA certificate and the nickname to use when storing the certificate in the NSS database.
    • Be sure to keep the parameters in their respective stanzas.
  • Then use pkispawn to create the new CA.
  script -c 'pkispawn -s CA -f ca.cfg -vvv'

Import the old data into the new CA database

  • Before the old data can be imported into the new CA database, this data needs to be cleaned up. We will do this by modifying the LDIF backup ifle created in the steps above. The primary reason for this is that in more recent versions of the Directory Server (RHDS 10+), syntax checking is enabled by default. Current versions of Dogtag have been fixed to account for syntax checking, and it is recommended that new Dogtag versions run against a DS where syntax checking is enabled. Because syntax checking was not previously required, the old data may include entries which would be invalid in new databases. In particular, we have seen issues related to the following syntax rules:
    • Attributes of Boolean type must be either TRUE or FALSE (all caps). Places where the value is not all caps, will be invalid. Note that simply changing all parameters globally from "true" to TRUE, and "false" to "FALSE" will result in incorrect results, as it will affect entries that are not Boolean type. Fortunately, only a couple of parameters in older databases are of Boolean type - Clone and DomainManager, both of which are found in security domain database entries. Look for these entries under cn=CAList,ou=Security Domain,o={basedn}.
    • Attributes of Directory String type cannot be blank. In this case, the solution is simply to remove the line corresponding to that attribute from the old data LDIF. There are many attributes of these types. In practice though, we have found that blank values have been found in "userType" and "userState" attributes in cmsUser entries in ou= People, {basedn}.
    • Other entries may fail syntax checks. It will be important to check the import log after performing the database import; look for "ldap_add: Invalid syntax (21)". Alternatively, you could do a test import into an empty database ahead of time to see which entries fail to be added.
  • Now shut down the CA and back up the database (just in case).
  systemctl stop [email protected]
  db2bak
  • Remove the certificate entry for the signing certificate from the new RHEL 7 internal database. This will be "cn={serial_no},ou=certificateRepository,ou=ca,o=pki-tomcat-CA", where {serial_no} is the serial number of the imported signing certificate. We need to do this to ensure that the CRL attributes in that entry will point to the correct entries. Removing the entry from the new database ensures that the entry will be imported from the old data.
  ldapdelete -x -w netscape -D 'cn=Directory Manager' "cn={serial_no},ou=certificateRepository,ou=ca,o=pki-tomcat-CA"
  • Import the old data into the new database:
  script -c "ldapmodify -x -w netscape -D 'cn=Directory Manager' -a -c -f ~/old_ca.ldif"
  • The ldapmdify command will only add entries that are new. Existing entries will be untouched. The typescript output for this command shows the invocation results and is shown below. I have annotated the results to highlight things to look for. In general, though:
    • We expect to see container objects (like the top level DN for instance) to be excluded as they already exist.
    • We expect to see an entry already for the signing certificate. This entry was created as part of importing the old signing certificate and key.
  adding new entry "dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  adding new entry "ou=people,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  adding new entry "ou=groups,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  <span style="color:green">
  '' These next few entries are standard groups.  They should already exist.''
  '' If you have added custom groups, then these should be added with no issues.''
  '' One side effect of these entries not being added is that any users are assigned ''
  '' to these groups in the old database will not be added.  You will need to add these ''
  '' user group associations afterwards (using ldapmodify, the console or the pki CLI).
  </span>
  
  adding new entry "cn=Certificate Manager Agents,ou=groups,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  adding new entry "cn=Registration Manager Agents,ou=groups,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  adding new entry "cn=Subsystem Group,ou=groups,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  adding new entry "cn=Trusted Managers,ou=groups,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  adding new entry "cn=Administrators,ou=groups,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  adding new entry "cn=Auditors,ou=groups,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  adding new entry "cn=ClonedSubsystems,ou=groups,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  adding new entry "cn=Security Domain Administrators,ou=groups,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  adding new entry "cn=Enterprise CA Administrators,ou=groups,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  adding new entry "cn=Enterprise KRA Administrators,ou=groups,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  adding new entry "cn=Enterprise OCSP Administrators,ou=groups,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  adding new entry "cn=Enterprise TKS Administrators,ou=groups,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  adding new entry "cn=Enterprise RA Administrators,ou=groups,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  adding new entry "cn=Enterprise TPS Administrators,ou=groups,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  <span style="color:green">'' These again are top level containers.  They should exist already.''</span>
  
  adding new entry "ou=requests,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  adding new entry "cn=crossCerts,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  adding new entry "ou=ca,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  adding new entry "ou=certificateRepository,ou=ca,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  adding new entry "ou=crlIssuingPoints,ou=ca,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  adding new entry "ou=ca,ou=requests,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  adding new entry "ou=replica,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  adding new entry "ou=requests,ou=ranges,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  adding new entry "ou=certificateRepository,ou=ranges,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  <span style="color:green">
  '' This entry contains all the acls for the CA, and will contain the default acls ''
  '' as provided by the installer.  If there are customized acls, they will need to ''
  '' be merged in, either using ldapmodify or using the console. ''
  </span>
  
  adding new entry "cn=aclResources,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  <span style="color:green">
  '' This is the entry for the signing certificate.  It will have been recreated during the ''
  '' installation process when the signing keys and certs are imported. '' </span>
  
  adding new entry "cn=1,ou=certificateRepository,ou=ca,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  <span style="color:green">
  ''  This is data for all the old requests and certificates.  This is what we want to import! ''
  '' In fact, more likely than not, this will be the requests and certs for the old CA's ''
  '' system certificates.'' </span>
  
  adding new entry "cn=1,ou=ca,ou=requests,dc=host1.example.com-pki-rootCA"
  
  adding new entry "cn=2,ou=certificateRepository,ou=ca,dc=host1.example.com-pki-rootCA"
  
  adding new entry "cn=2,ou=ca,ou=requests,dc=host1.example.com-pki-rootCA"
  
  adding new entry "cn=3,ou=certificateRepository,ou=ca,dc=host1.example.com-pki-rootCA"
  
  adding new entry "cn=3,ou=ca,ou=requests,dc=host1.example.com-pki-rootCA"
  
  adding new entry "cn=4,ou=certificateRepository,ou=ca,dc=host1.example.com-pki-rootCA"
  
  adding new entry "cn=4,ou=ca,ou=requests,dc=host1.example.com-pki-rootCA"
  
  <span style="color:green">
  '' This is the old subsystem user.  This is a user that is used to co-ordinate communication with
  '' other subsystems using the old subsystem certificate as a credential.  For example, this user will
  '' be the one mapped to in a CA-KRA connector.  ''
  '' A new subsystem user will have been created during the install, but you want to keep this user ''
  '' around for current connectors.'' </span>
  
  adding new entry "uid=CA-host1.example.com-9443,ou=People,dc=host1.example.com-pki-rootCA"
  
  adding new entry "cn=5,ou=certificateRepository,ou=ca,dc=host1.example.com-pki-rootCA"
  
  adding new entry "cn=5,ou=ca,ou=requests,dc=host1.example.com-pki-rootCA"
  
  <span style="color:green">
  '' This is the old admin user.  This is the bootstrap user which is created by default in ''
  '' the old CA install.  This user entry may still exist unless you have removed it from the ''
  '' old subsystem.  Note that there is no conflict here.  This is because, by default, in Dogtag 10, ''
  '' the bootstrap user is named 'caadmin' instead of just admin.  ''
  '' Any other users that have been added to the old CA should be added here without issues. ''
  '' Any users imported here will have not be assigned to any groups.  See the note above.''
  </span>
  
  adding new entry "uid=admin,ou=People,dc=host1.example.com-pki-rootCA"
  
  adding new entry "cn=6,ou=certificateRepository,ou=ca,dc=host1.example.com-pki-rootCA"
  
  adding new entry "cn=6,ou=ca,ou=requests,dc=host1.example.com-pki-rootCA"
  
  <span style="color:green">'' More top-level containers (related to the security domain).  We expect these to already exist.'' </span>
  
  adding new entry "ou=Security Domain,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  adding new entry "cn=CAList,ou=Security Domain,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  adding new entry "cn=OCSPList,ou=Security Domain,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  adding new entry "cn=KRAList,ou=Security Domain,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  adding new entry "cn=RAList,ou=Security Domain,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  adding new entry "cn=TKSList,ou=Security Domain,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  adding new entry "cn=TPSList,ou=Security Domain,dc=host1.example.com-pki-rootCA"
  ldap_add: Already exists (68)
  
  <span style="color:green">
  '' This is the old entry for the security domain for this CA.  This entry is no longer valid ''
  '' and we will need to delete it '' </span>
  adding new entry "cn=host1.example.com:9445,cn=CAList,ou=Security Domain,dc=host1.example.com-pki-rootCA"
  
  <span style="color:green">'' This entry should be added without error if pki_master_crl_enable is set to False ''</span>
  adding new entry "cn=MasterCRL,ou=crlIssuingPoints,ou=ca,dc=host1.example.com-pki-rootCA"
  
  <span style="color:green">
  '' requests and certs from the old CA.  If you calculated the serial numbers correctly, there should ''
  '' be no duplicates. ''</span>
  adding new entry "cn=7,ou=ca,ou=requests,dc=host1.example.com-pki-rootCA"
  
  adding new entry "cn=8,ou=ca,ou=requests,dc=host1.example.com-pki-rootCA"
  
  adding new entry "cn=9,ou=ca,ou=requests,dc=host1.example.com-pki-rootCA"
  
  adding new entry "cn=10,ou=ca,ou=requests,dc=host1.example.com-pki-rootCA"
  ...
  
  adding new entry "cn=7,ou=certificateRepository,ou=ca,dc=host1.example.com-pki-rootCA"
  
  adding new entry "cn=8,ou=certificateRepository,ou=ca,dc=host1.example.com-pki-rootCA"
  
  adding new entry "cn=9,ou=certificateRepository,ou=ca,dc=host1.example.com-pki-rootCA"
  
  adding new entry "cn=10,ou=certificateRepository,ou=ca,dc=host1.example.com-pki-rootCA"
  ..

  • As mentioned above, the old security domain entry for the CA should be removed.
  ldapmodify -w netscape -x -D "cn=Directory Manager"
  dn: cn=host1.example.com:9445,cn=CAList,ou=Security Domain,dc=host1.example.com-pki-rootCA
  changetype: delete

Final Adjustments

  • In my case, there were no customized UI, profiles or plugins. These should be added either by copying in the required files or by using the console.
  • Enable CRL publishing by setting ca.crl.MasterCRL.enable=true in CS.cfg
  • Restart the CA.
  systemctl restart [email protected]
  • As mentioned above, users and ACLs will not have been migrated. They should now be added using the console, ldapmodify or the pki CLI. As an example, I provide the steps used to restore the admin user to his former groups using the CLI.
  # Fix admin user
  pki -n "PKI Administrator for example.com" -c netscape user-show admin
  pki -n "PKI Administrator for example.com" -c netscape group-find
  pki -n "PKI Administrator for example.com" -c netscape user-membership-add admin "Certificate Manager Agents"
  pki -n "PKI Administrator for example.com" -c netscape user-membership-add admin "Administrators"
  pki -n "PKI Administrator for example.com" -c netscape user-membership-add admin "Security Domain Administrators"
  etc. ...
⚠️ **GitHub.com Fallback** ⚠️