Skip to content

Migrating CA using Existing CA Mechanism

Endi S. Dewata edited this page Sep 14, 2023 · 3 revisions

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 PKCS12 File.

This is based on the page Migrating a CA with HSM using existing CA mechanism, except that the HSM specific instructions have been removed. In this case, the system keys will be stored in the software-based NSS database.

The basics of the migration is as follows:

Existing CA

  • RHEL 5 RHCS 8 (Advanced)
  • Root CA, not a clone.
  • All system keys are in nssdb at /var/lib/{instance_name}/alias.
  • 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)
  • Root CA uses the same CA signing certificate and key. This key is brought over using a pkcs12 file.
  • All other system certificates (ocsp signing cert, subsystem cert, audit signing cert, etc.) are generated anew.
  • Database is local to the CA. Data from the existing CA has been migrated over.

General Steps

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

Specifics

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 signing certificate and key, I used the following commands:
  echo Secret.123 > password.txt  # this is the PKCS12 password
  grep internal= /var/lib/pki-rootCA/conf/password.conf | awk -F= '{print $2;}' > internal.txt
  PKCS12Export -d /var/lib/pki-rootCA/alias -p internal.txt -o ca.p12 -w password.txt
  • To export the 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.p12, ca_signing.csr) were transferred to the new CA machine. If this is an intermediate CA, also transfer the 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).

The PKCS12 file we copied over contains all the system certificates for the old system. Verify that the file includes the CA signing certificate and the key with the following commands.

Note: in FIPS mode the pki pkcs12 commands need to be executed with an NSS database. See PKI PKCS12 CLI.

 $ echo "<font color="red">Secret.123</font>" > password.txt
 $ pki pkcs12-cert-find --pkcs12-file ca.p12 --pkcs12-password-file password.txt 
 $ pki pkcs12-key-find --pkcs12-file ca.p12 --pkcs12-password-file password.txt

Verify the trust flags of the CA signing certificate. If it's not "CTu,Cu,Cu" or it's missing, it can be fixed with the following command:

 $ pki pkcs12-cert-mod "<font color="red">caSigningCert cert-pki-rootCA</font>" --pkcs12-file ca.p12 --pkcs12-password-file password.txt --trust-flags "CTu,Cu,Cu"

We need only the CA signing certificate and key. Therefore, we need to remove the other certs and keys using the following commands:

 $ pki pkcs12-cert-del "<font color="red">Server-Cert cert-pki-rootCA</font>" --pkcs12-file ca.p12 --pkcs12-password-file password.txt
 $ pki pkcs12-cert-del "<font color="red">subsystemCert cert-pki-rootCA</font>" --pkcs12-file ca.p12 --pkcs12-password-file password.txt
 $ pki pkcs12-cert-del "<font color="red">ocspSigningCert cert-pki-rootCA</font>" --pkcs12-file ca.p12 --pkcs12-password-file password.txt
 $ pki pkcs12-cert-del "<font color="red">auditSigningCert cert-pki-rootCA</font>" --pkcs12-file ca.p12 --pkcs12-password-file password.txt

If the CA being migrated is also an intermediate CA, remove the root CA cert from the PKCS#12 file as well.

 $ pki pkcs12-cert-del "<font color="red">Top-level Root CA Signing Certificate</font>" --pkcs12-file ca.p12 --pkcs12-password-file password.txt

I created a pkispawn file as below:

 [CA]
 pki_ca_signing_nickname=caSigningCert cert-pki-rootCA
 pki_ca_signing_csr_path=/root/transfer_data/ca_signing.csr
 pki_pkcs12_path=/root/transfer_data/ca.p12
 pki_pkcs12_password=Secret.123
 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 can be whatever you like ie. it can be the same or different from the old CA.
  • The pki_ca_signing_nickname must match the CA signing certificate's nickname in the PKCS #12 file.
  • pki_existing = True means that we will be using the existing CA mechanism.
  • The pki_ca_signing_* and pki_pkcs12_* parameters point to the files we copied over from the old machine. The ca.p12 is the processed PKCS#12 file containing only the signing certificate and 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 pki-tomcatd@pki-tomcat-rootCA.service
  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 pki-tomcatd@pki-tomcat-rootCA.service
  • 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. ...
Clone this wiki locally