From fd3cfd86c43621f6be68fd15f3abeb9a8de53d7f Mon Sep 17 00:00:00 2001 From: Marco Fargetta Date: Tue, 17 Sep 2024 09:25:35 +0200 Subject: [PATCH] Modify pkispawn to deploy EST EST deployment is included in pkispwn. The installation does not perform all the steps done for CA and other subsystems so there is no security domain management and user administration. During the installation there is no DS or other DBs connection which has to be performed by the user before or after the installation. --- .github/workflows/est-ds-realm-test.yml | 322 + .github/workflows/est-tests.yml | 6 + base/est/CMakeLists.txt | 21 + base/est/bin/estauthz | 7 + base/est/conf/realm.conf | 1 - base/est/shared/authorizer.conf | 2 + base/est/shared/backend.conf | 5 + base/est/shared/realm/ds.conf | 7 + base/est/shared/realm/in-memory.conf | 4 + base/est/shared/realm/postgresql.conf | 5 + base/est/shared/realm/statements.conf | 31 + base/est/webapps/est/index.jsp | 25 + base/server/etc/#default.cfg# | 690 ++ base/server/etc/.#default.cfg | 1 + base/server/etc/default.cfg | 29 +- base/server/examples/installation/est.cfg | 9 + .../pki/server/deployment/#__init__.py# | 5625 +++++++++++++++++ .../pki/server/deployment/.#__init__.py | 1 + .../python/pki/server/deployment/__init__.py | 133 +- .../python/pki/server/deployment/pkiconfig.py | 2 +- .../deployment/scriptlets/initialization.py | 6 +- base/server/python/pki/server/pkispawn.py | 19 +- base/server/python/pki/server/subsystem.py | 102 + pki.spec | 1 + 24 files changed, 7043 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/est-ds-realm-test.yml create mode 100755 base/est/bin/estauthz delete mode 100644 base/est/conf/realm.conf create mode 100644 base/est/shared/authorizer.conf create mode 100644 base/est/shared/backend.conf create mode 100644 base/est/shared/realm/ds.conf create mode 100644 base/est/shared/realm/in-memory.conf create mode 100644 base/est/shared/realm/postgresql.conf create mode 100644 base/est/shared/realm/statements.conf create mode 100644 base/est/webapps/est/index.jsp create mode 100644 base/server/etc/#default.cfg# create mode 120000 base/server/etc/.#default.cfg create mode 100644 base/server/examples/installation/est.cfg create mode 100644 base/server/python/pki/server/deployment/#__init__.py# create mode 120000 base/server/python/pki/server/deployment/.#__init__.py diff --git a/.github/workflows/est-ds-realm-test.yml b/.github/workflows/est-ds-realm-test.yml new file mode 100644 index 00000000000..4df14d05ac0 --- /dev/null +++ b/.github/workflows/est-ds-realm-test.yml @@ -0,0 +1,322 @@ +name: EST with ds realm + +on: workflow_call + +env: + DB_IMAGE: ${{ vars.DB_IMAGE || 'quay.io/389ds/dirsrv' }} + +jobs: + # docs/installation/ca/Installing_CA.md + test: + name: Test + runs-on: ubuntu-latest + env: + SHARED: /tmp/workdir/pki + steps: + - name: Clone repository + uses: actions/checkout@v4 + + - name: Retrieve PKI images + uses: actions/cache@v4 + with: + key: pki-images-${{ github.sha }} + path: pki-images.tar + + - name: Load PKI images + run: docker load --input pki-images.tar + + - name: Create network + run: docker network create example + + - name: Set up DS container + run: | + tests/bin/ds-create.sh \ + --image=${{ env.DB_IMAGE }} \ + --hostname=ds.example.com \ + --password=Secret.123 \ + ds + + - name: Connect DS container to network + run: docker network connect example ds --alias ds.example.com + + - name: Set up PKI container + run: | + tests/bin/runner-init.sh pki + env: + HOSTNAME: pki.example.com + + - name: Connect PKI container to network + run: docker network connect example pki --alias pki.example.com + + - name: Install CA + run: | + docker exec pki pkispawn \ + -f /usr/share/pki/server/examples/installation/ca.cfg \ + -s CA \ + -D pki_ds_url=ldap://ds.example.com:3389 \ + -v + + - name: Initialize PKI client + run: | + docker exec pki pki-server cert-export ca_signing --cert-file ca_signing.crt + + docker exec pki pki nss-cert-import \ + --cert ca_signing.crt \ + --trust CT,C,C \ + ca_signing + + docker exec pki pki pkcs12-import \ + --pkcs12 /root/.dogtag/pki-tomcat/ca_admin_cert.p12 \ + --pkcs12-password Secret.123 + + docker exec pki pki info + + - name: Add CA EST user + run: | + docker exec pki pki -n caadmin ca-group-add "EST RA Agents" + docker exec pki pki -n caadmin ca-user-add \ + est-ra-1 --fullName "EST RA 1" --password Secret.est + docker exec pki pki -n caadmin ca-group-member-add "EST RA Agents" est-ra-1 + + - name: Configure CA est profile + run: | + docker exec pki cp /usr/share/pki/ca/profiles/ca/estServiceCert.cfg estServiceCert.cfg + docker exec pki sed -i 's/EST RA Agents/Subsystem Group/' estServiceCert.cfg + docker exec pki pki -n caadmin ca-profile-add \ + --raw ./estServiceCert.cfg + docker exec pki pki -n caadmin ca-profile-enable estServiceCert + docker exec pki pki-server restart --wait + + - name: Install EST + run: | + docker exec pki pkispawn \ + -f /usr/share/pki/server/examples/installation/est.cfg \ + -s EST \ + -D est_realm_url=ldap://ds.example.com:3389 \ + -v + + - name: Check EST backend config + if: always() + run: | + docker exec pki cat /etc/pki/pki-tomcat/est/backend.conf + + - name: Check EST authorizer config + if: always() + run: | + docker exec pki cat /etc/pki/pki-tomcat/est/authorizer.conf + + - name: Check EST realm config + if: always() + run: | + docker exec pki cat /etc/pki/pki-tomcat/est/realm.conf + + - name: Check webapps + run: | + docker exec pki pki-server webapp-find | tee output + + # CA instance should have ROOT, ca, and pki webapps + echo "ROOT" > expected + echo "ca" >> expected + echo "est" >> expected + echo "pki" >> expected + sed -n 's/^ *Webapp ID: *\(.*\)$/\1/p' output > actual + diff expected actual + + docker exec pki pki-server webapp-show ROOT + docker exec pki pki-server webapp-show ca + docker exec pki pki-server webapp-show est + docker exec pki pki-server webapp-show pki + + - name: Create EST users + run: | + docker exec -i pki ldapadd -x -H ldap://ds.example.com:3389 \ + -D "cn=Directory Manager" -w Secret.123 << EOF + dn: dc=est,dc=pki,dc=example,dc=com + objectClass: domain + dc: est + + dn: ou=people,dc=est,dc=pki,dc=example,dc=com + ou: people + objectClass: top + objectClass: organizationalUnit + + dn: ou=groups,dc=est,dc=pki,dc=example,dc=com + ou: groups + objectClass: top + objectClass: organizationalUnit + + dn: uid=est-test-user,ou=people,dc=est,dc=pki,dc=example,dc=com + objectClass: top + objectClass: person + objectClass: organizationalPerson + objectClass: inetOrgPerson + objectClass: cmsuser + uid: est-test-user + sn: EST TEST USER + cn: EST TEST USER + usertype: undefined + userPassword: Secret.123 + + dn: cn=estclient,ou=groups,dc=est,dc=pki,dc=example,dc=com + objectClass: top + objectClass: groupOfUniqueNames + cn: estclient + uniqueMember: uid=est-test-user,ou=People,dc=est,dc=pki,dc=example,dc=com + EOF + + - name: Check EST subsystem + run: | + docker exec pki pki-server subsystem-show est | tee output + + # CA instance should have CA subsystem + echo "est" > expected + sed -n 's/^ *Subsystem ID: *\(.*\)$/\1/p' output > actual + diff expected actual + + echo "True" > expected + sed -n 's/^ *Enabled: *\(.*\)$/\1/p' output > actual + diff expected actual + + - name: Test CA certs + run: | + docker exec pki curl -o cacert.p7 -k https://pki.example.com:8443/.well-known/est/cacerts + docker exec pki openssl base64 -d --in cacert.p7 --out cacert.p7.der + docker exec pki openssl pkcs7 --in cacert.p7.der -inform DER -print_certs -out cacert.pem + docker exec pki openssl x509 -in cacert.pem -text -noout | tee actual + docker exec pki openssl x509 -in ca_signing.crt -text -noout | tee expected + diff expected actual + + - name: Install est client + run: | + docker exec pki dnf copr enable -y @pki/libest + docker exec pki dnf install -y libest + + - name: Enroll certificate + run: | + docker exec -e EST_OPENSSL_CACERT=cacert.pem pki estclient -e -s pki.example.com -p 8443 \ + --common-name test.example.com -o . -u est-test-user -h Secret.123 + + docker exec pki openssl base64 -d --in cert-0-0.pkcs7 --out cert-0-0.pkcs7.der + docker exec pki openssl pkcs7 -in cert-0-0.pkcs7.der -inform DER -print_certs -out cert.pem + docker exec pki openssl x509 -in cert.pem -subject -noout | tee actual + echo "subject=CN=test.example.com" > expected + diff expected actual + + - name: Remove EST + run: | + docker exec pki pki-server est-undeploy --wait + docker exec pki pki-server est-remove + + - name: Remove CA + run: docker exec pki pkidestroy -i pki-tomcat -s CA -v + + - name: Check PKI server base dir after removal + run: | + # check file types, owners, and permissions + docker exec pki ls -l /var/lib/pki/pki-tomcat \ + | sed \ + -e '/^total/d' \ + -e 's/^\(\S*\) *\S* *\(\S*\) *\(\S*\) *\S* *\S* *\S* *\S* *\(.*\)$/\1 \2 \3 \4/' \ + | tee output + + # TODO: review permissions + cat > expected << EOF + lrwxrwxrwx pkiuser pkiuser conf -> /etc/pki/pki-tomcat + lrwxrwxrwx pkiuser pkiuser logs -> /var/log/pki/pki-tomcat + EOF + + diff expected output + + - name: Check PKI server conf dir after removal + run: | + # check file types, owners, and permissions + docker exec pki ls -l /etc/pki/pki-tomcat \ + | sed \ + -e '/^total/d' \ + -e 's/^\(\S*\) *\S* *\(\S*\) *\(\S*\) *\S* *\S* *\S* *\S* *\(.*\)$/\1 \2 \3 \4/' \ + | tee output + + # TODO: review permissions + cat > expected << EOF + drwxrwx--- pkiuser pkiuser Catalina + drwxrwx--- pkiuser pkiuser alias + drwxrwx--- pkiuser pkiuser ca + -rw-r--r-- pkiuser pkiuser catalina.policy + lrwxrwxrwx pkiuser pkiuser catalina.properties -> /usr/share/pki/server/conf/catalina.properties + drwxrwx--- pkiuser pkiuser certs + lrwxrwxrwx pkiuser pkiuser context.xml -> /etc/tomcat/context.xml + drwxrwx--- pkiuser pkiuser est + lrwxrwxrwx pkiuser pkiuser logging.properties -> /usr/share/pki/server/conf/logging.properties + -rw-rw---- pkiuser pkiuser password.conf + -rw-rw---- pkiuser pkiuser server.xml + -rw-rw---- pkiuser pkiuser serverCertNick.conf + -rw-rw---- pkiuser pkiuser tomcat.conf + lrwxrwxrwx pkiuser pkiuser web.xml -> /etc/tomcat/web.xml + EOF + + diff expected output + + - name: Check PKI server logs dir after removal + run: | + # check file types, owners, and permissions + docker exec pki ls -l /var/log/pki/pki-tomcat \ + | sed \ + -e '/^total/d' \ + -e 's/^\(\S*\) *\S* *\(\S*\) *\(\S*\) *\S* *\S* *\S* *\S* *\(.*\)$/\1 \2 \3 \4/' \ + | tee output + + DATE=$(date +'%Y-%m-%d') + + # TODO: review permissions + cat > expected << EOF + drwxr-x--- pkiuser pkiuser backup + drwxrwx--- pkiuser pkiuser ca + -rw-rw-r-- pkiuser pkiuser catalina.$DATE.log + drwxrwx--- pkiuser pkiuser est + -rw-rw-r-- pkiuser pkiuser host-manager.$DATE.log + -rw-rw-r-- pkiuser pkiuser localhost.$DATE.log + -rw-r--r-- pkiuser pkiuser localhost_access_log.$DATE.txt + -rw-rw-r-- pkiuser pkiuser manager.$DATE.log + drwxr-xr-x pkiuser pkiuser pki + EOF + + diff expected output + + - name: Check DS server systemd journal + if: always() + run: | + docker exec ds journalctl -x --no-pager -u dirsrv@localhost.service + + - name: Check DS container logs + if: always() + run: | + docker logs ds + + - name: Check PKI server systemd journal + if: always() + run: | + docker exec pki journalctl -x --no-pager -u pki-tomcatd@pki-tomcat.service + + - name: Check CA debug log + if: always() + run: | + docker exec pki find /var/lib/pki/pki-tomcat/logs/ca -name "debug.*" -exec cat {} \; + + - name: Check EST debug log + if: always() + run: | + docker exec pki find /var/lib/pki/pki-tomcat/logs/est -name "debug.*" -exec cat {} \; + + - name: Gather artifacts + if: always() + run: | + tests/bin/ds-artifacts-save.sh ds + tests/bin/pki-artifacts-save.sh pki + continue-on-error: true + + - name: Upload artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: ca-basic + path: /tmp/artifacts diff --git a/.github/workflows/est-tests.yml b/.github/workflows/est-tests.yml index 0f195a24a61..4d052e56ad9 100644 --- a/.github/workflows/est-tests.yml +++ b/.github/workflows/est-tests.yml @@ -40,3 +40,9 @@ jobs: ansible-playbook -e 'pki_subsystem="est"' tests/ansible/pki-playbook.yml env: ANSIBLE_CONFIG: ${{ github.workspace }}/tests/ansible/ansible.cfg + + + est-ds-realm-test: + name: EST with ds realm + needs: build + uses: ./.github/workflows/est-ds-realm-test.yml diff --git a/base/est/CMakeLists.txt b/base/est/CMakeLists.txt index 4cace31f0d2..73e7f756ef2 100644 --- a/base/est/CMakeLists.txt +++ b/base/est/CMakeLists.txt @@ -96,3 +96,24 @@ install( DESTINATION ${DATA_INSTALL_DIR}/est/webapps/est/WEB-INF/lib ) + +install( + DIRECTORY + shared/ + DESTINATION + ${SHARE_INSTALL_PREFIX}/${APPLICATION_NAME}/${PROJECT_NAME}/conf/ + PATTERN + "CMakeLists.txt" EXCLUDE +) + +install( + FILES + ${CMAKE_CURRENT_SOURCE_DIR}/bin/estauthz + DESTINATION + ${LIBEXEC_INSTALL_DIR} + PERMISSIONS + OWNER_EXECUTE OWNER_WRITE OWNER_READ + GROUP_EXECUTE GROUP_READ + WORLD_EXECUTE WORLD_READ + +) diff --git a/base/est/bin/estauthz b/base/est/bin/estauthz new file mode 100755 index 00000000000..3fce98cfa1c --- /dev/null +++ b/base/est/bin/estauthz @@ -0,0 +1,7 @@ +#!/usr/bin/python3 +import json, sys +ALLOWED_ROLE = 'estclient' +obj = json.loads(sys.stdin.read()) +if not ALLOWED_ROLE in obj['authzData']['principal']['roles']: + print(f'Principal does not have required role {ALLOWED_ROLE!r}') + sys.exit(1) diff --git a/base/est/conf/realm.conf b/base/est/conf/realm.conf deleted file mode 100644 index ccfb9d62813..00000000000 --- a/base/est/conf/realm.conf +++ /dev/null @@ -1 +0,0 @@ -class=org.apache.catalina.realm.MemoryRealm diff --git a/base/est/shared/authorizer.conf b/base/est/shared/authorizer.conf new file mode 100644 index 00000000000..bb126819b72 --- /dev/null +++ b/base/est/shared/authorizer.conf @@ -0,0 +1,2 @@ +class=org.dogtagpki.est.ExternalProcessRequestAuthorizer +executable=/usr/local/libexec/estauthz diff --git a/base/est/shared/backend.conf b/base/est/shared/backend.conf new file mode 100644 index 00000000000..83eeeaa7f64 --- /dev/null +++ b/base/est/shared/backend.conf @@ -0,0 +1,5 @@ +class=org.dogtagpki.est.DogtagRABackend +url=https://fedora:8443 +profile=estServiceCert +username=est-ra-1 +password=est4ever diff --git a/base/est/shared/realm/ds.conf b/base/est/shared/realm/ds.conf new file mode 100644 index 00000000000..150bebfa934 --- /dev/null +++ b/base/est/shared/realm/ds.conf @@ -0,0 +1,7 @@ +class=com.netscape.cms.realm.PKILDAPRealm +url=ldap://localhost.localdomain:389 +authType=BasicAuth +bindDN=cn=Directory Manager +bindPassword=Secret.123 +usersDN=ou=people,dc=est,dc=pki,dc=example,dc=com +groupsDN=ou=groups,dc=est,dc=pki,dc=example,dc=com \ No newline at end of file diff --git a/base/est/shared/realm/in-memory.conf b/base/est/shared/realm/in-memory.conf new file mode 100644 index 00000000000..6182f91923d --- /dev/null +++ b/base/est/shared/realm/in-memory.conf @@ -0,0 +1,4 @@ +class=com.netscape.cms.realm.PKIInMemoryRealm +username=admin +password=Secret.123 +roles=estclient \ No newline at end of file diff --git a/base/est/shared/realm/postgresql.conf b/base/est/shared/realm/postgresql.conf new file mode 100644 index 00000000000..8be846d45a5 --- /dev/null +++ b/base/est/shared/realm/postgresql.conf @@ -0,0 +1,5 @@ +class=com.netscape.cms.realm.PKIPostgreSQLRealm +url=jdbc:postgresql://localhost.localdomain:5432/est +user=est +password=Secret.123 +statements=/usr/share/pki/est/conf/realm/statements.conf \ No newline at end of file diff --git a/base/est/shared/realm/statements.conf b/base/est/shared/realm/statements.conf new file mode 100644 index 00000000000..f8cc8132942 --- /dev/null +++ b/base/est/shared/realm/statements.conf @@ -0,0 +1,31 @@ +getUserByID=\ +SELECT \ + "id", "full_name", "password" \ +FROM \ + "users" \ +WHERE \ + "id" = ? + +getUserByCertID=\ +SELECT \ + u."id", u."full_name", u."password" \ +FROM \ + "users" u, "user_certs" uc \ +WHERE \ + u."id" = uc."user_id" AND uc."cert_id" = ? + +getUserCerts=\ +SELECT \ + "data" \ +FROM \ + "user_certs" \ +WHERE \ + "user_id" = ? + +getUserRoles=\ +SELECT \ + "group_id" \ +FROM \ + "group_members" \ +WHERE \ + "user_id" = ? \ No newline at end of file diff --git a/base/est/webapps/est/index.jsp b/base/est/webapps/est/index.jsp new file mode 100644 index 00000000000..79dca917a14 --- /dev/null +++ b/base/est/webapps/est/index.jsp @@ -0,0 +1,25 @@ + + + + Enrollment over Secure Transport + + + + + diff --git a/base/server/etc/#default.cfg# b/base/server/etc/#default.cfg# new file mode 100644 index 00000000000..2f3a7605b40 --- /dev/null +++ b/base/server/etc/#default.cfg# @@ -0,0 +1,690 @@ +############################################################################### +## Default Configuration: ## +## ## +## Values in this section are common to more than one PKI subsystem, and ## +## contain required information which MAY be overridden by users as ## +## necessary. ## +## ## +## There are also some meta-parameters that determine how the PKI ## +## configuratiion should work. ## +## ## +############################################################################### +[DEFAULT] + +JAVA_HOME=%(java_home)s + +# The sensitive_parameters contains a list of parameters which may contain +# sensitive information which must not be displayed to the console nor stored +# in log files for security reasons. +sensitive_parameters= + pki_admin_password + pki_backup_password + pki_client_database_password + pki_client_pin + pki_client_pkcs12_password + pki_clone_pkcs12_password + pki_ds_password + pki_external_pkcs12_password + pki_pkcs12_password + pki_one_time_pin + pki_pin + pki_replication_password + pki_security_domain_password + pki_server_database_password + pki_server_pkcs12_password + pki_token_password + acme_database_bind_password + acme_database_password + acme_issuer_password + est_realm_bind_password + est_realm_password + est_ca_password + +pki_instance_name=pki-tomcat +pki_http_port=8080 +pki_https_port=8443 + +pki_admin_setup=True +pki_admin_cert_file=%(pki_client_dir)s/ca_admin.cert +pki_admin_cert_request_type=pkcs10 +pki_admin_dualkey=False +pki_admin_key_algorithm=SHA256withRSA +# DEPRECATED: Use 'pki_admin_key_size' instead. +pki_admin_keysize=2048 +pki_admin_key_size=%(pki_admin_keysize)s +pki_admin_key_type=rsa +pki_admin_password= + +pki_audit_group=pkiaudit + +pki_audit_signing_key_algorithm=SHA256withRSA +pki_audit_signing_key_size=2048 +pki_audit_signing_key_type=rsa +pki_audit_signing_signing_algorithm=SHA256withRSA +pki_audit_signing_token= +pki_audit_signing_opFlags= +pki_audit_signing_opFlagsMask= + +pki_backup_keys=False +pki_backup_file= +pki_backup_password= + +pki_ca_signing_nickname=caSigningCert cert-%(pki_instance_name)s CA + +# DEPRECATED: Use 'pki_ca_signing_cert_path' instead. +pki_external_ca_cert_path= +pki_ca_signing_cert_path=%(pki_external_ca_cert_path)s + +pki_client_admin_cert_p12=%(pki_client_dir)s/%(pki_subsystem_type)s_admin_cert.p12 +pki_client_database_password= +pki_client_database_purge=True +pki_client_dir=%(home_dir)s/.dogtag/%(pki_instance_name)s +pki_client_pkcs12_password= +pki_ds_bind_dn=cn=Directory Manager +pki_ds_create_new_db=True +pki_ds_ldap_port=389 +pki_ds_ldaps_port=636 +pki_ds_password= +pki_ds_remove_data=True +pki_ds_secure_connection=False +pki_ds_secure_connection_ca_nickname=Directory Server CA certificate +pki_ds_secure_connection_ca_pem_file= +pki_group=pkiuser +pki_hsm_enable=False +pki_hsm_libfile= +pki_hsm_modulename= +pki_issuing_ca_hostname=%(pki_security_domain_hostname)s +pki_issuing_ca_https_port=%(pki_security_domain_https_port)s +pki_issuing_ca_uri=https://%(pki_issuing_ca_hostname)s:%(pki_issuing_ca_https_port)s +pki_issuing_ca=%(pki_issuing_ca_uri)s +pki_replication_password= +pki_status_request_timeout= + +pki_security_domain_hostname=%(pki_hostname)s +pki_security_domain_https_port=8443 +pki_security_domain_uri=https://%(pki_security_domain_hostname)s:%(pki_security_domain_https_port)s +pki_security_domain_name=%(pki_dns_domainname)s Security Domain +pki_security_domain_password= +pki_security_domain_user=caadmin + +#for supporting server cert SAN injection +pki_san_inject=False +pki_san_for_server_cert= +pki_skip_configuration=False +pki_skip_ds_verify=False +pki_skip_installation=False +pki_skip_sd_verify=False + +# DEPRECATED +# Use 'pki_sslserver_*' instead. +pki_ssl_server_key_algorithm=SHA256withRSA +pki_ssl_server_key_size=2048 +pki_ssl_server_key_type=rsa +pki_ssl_server_nickname=Server-Cert cert-%(pki_instance_name)s +pki_ssl_server_subject_dn=cn=%(pki_hostname)s,ou=%(pki_instance_name)s,o=%(pki_security_domain_name)s +pki_ssl_server_token= + +pki_sslserver_key_algorithm=%(pki_ssl_server_key_algorithm)s +pki_sslserver_signing_algorithm=SHA256withRSA +pki_sslserver_key_size=%(pki_ssl_server_key_size)s +pki_sslserver_key_type=%(pki_ssl_server_key_type)s +pki_sslserver_nickname=%(pki_ssl_server_nickname)s +pki_sslserver_subject_dn=%(pki_ssl_server_subject_dn)s +pki_sslserver_token=%(pki_ssl_server_token)s +pki_sslserver_opFlags= +pki_sslserver_opFlagsMask= + +pki_self_signed_nickname=temp %(pki_sslserver_nickname)s +pki_self_signed_token= + +pki_subsystem_key_algorithm=SHA256withRSA +pki_subsystem_signing_algorithm=SHA256withRSA +pki_subsystem_key_size=2048 +pki_subsystem_key_type=rsa +pki_subsystem_nickname=subsystemCert cert-%(pki_instance_name)s +pki_subsystem_subject_dn=cn=Subsystem Certificate,ou=%(pki_instance_name)s,o=%(pki_security_domain_name)s +pki_subsystem_token= +pki_subsystem_opFlags= +pki_subsystem_opFlagsMask= + +#Set this if we want to use PSS signing when RSA is specified +pki_use_pss_rsa_signing_algorithm=False +#Set this if we want ot use the OAEP key wrap alg. +pki_use_oaep_rsa_keywrap=False + +pki_token_name= +pki_token_password= +pki_user=pkiuser + +# DEPRECATED: Use 'pki_cert_chain_path' instead. +pki_external_ca_cert_chain_path= + +# In addition to specifying an external CA certificate, this parameter +# can be used with a one-shot installation process is used for installing +# non-CA subsystems on a new host, lacking any existing subsystems. This +# cert is used to establish trust to an existing CA installation on another +# system. +pki_cert_chain_path=%(pki_external_ca_cert_chain_path)s + +# DEPRECATED: Use 'pki_cert_chain_nickname' instead. +pki_external_ca_cert_chain_nickname=caSigningCert External CA +pki_cert_chain_nickname=%(pki_external_ca_cert_chain_nickname)s + +# DEPRECATED: Use 'pki_server_database_password' instead. +pki_pin= + +pki_pkcs12_path= +pki_pkcs12_password= + +# Paths: +# These are used in the processing of pkispawn and are not supposed +# to be overwritten by user configuration files. +# +pki_client_database_dir=%(pki_client_subsystem_dir)s/alias +pki_client_subsystem_dir=%(pki_client_dir)s/%(pki_subsystem_type)s +pki_client_password_conf=%(pki_client_subsystem_dir)s/password.conf +pki_client_pkcs12_password_conf=%(pki_client_subsystem_dir)s/pkcs12_password.conf +pki_client_admin_cert=%(pki_client_dir)s/%(pki_subsystem_type)s_admin.cert + +pki_instance_path=/var/lib/pki/%(pki_instance_name)s + +############################################################################### +## Tomcat Configuration: ## +## ## +## Values in this section are common to PKI subsystems that run ## +## as an instance of 'Tomcat' (CA, KRA, OCSP, TKS, and TPS subsystems ## +## including 'Clones', 'Subordinate CAs', and 'External CAs'), and contain ## +## required information which MAY be overridden by users as necessary. ## +## ## +## PKI CLONES: To specify a 'CA Clone', a 'KRA Clone', an 'OCSP Clone', ## +## a 'TKS Clone', or a 'TPS Clone', change the value of ## +## 'pki_clone' from 'False' to 'True'. ## +## ## +## REMINDER: PKI CA Clones, Subordinate CAs, and External CAs ## +## are MUTUALLY EXCLUSIVE entities!!! ## +############################################################################### +[Tomcat] +# Note: see Tomcat CVE 2020-1938. It is strongly recommended to leave +# pki_ajp_host as localhost. If trying to use AJP over a reverse proxy from +# another host, manually edit server.xml to specify a shared secret or +# tunnel it over a secure network. Refer to the Tomcat documentation for more +# information about secure Tomcat configuration. + +# Note: pki_ajp_host is deprecated in favor of pki_ajp_host_ipv4. +pki_ajp_host=localhost4 +pki_ajp_host_ipv4=%(pki_ajp_host)s +pki_ajp_host_ipv6=localhost6 + +pki_ajp_port=8009 +pki_ajp_secret=%(pki_random_ajp_secret)s +pki_server_pkcs12_path= +pki_server_pkcs12_password= +pki_server_external_certs_path= +pki_clone=False +pki_clone_pkcs12_password= +pki_clone_pkcs12_path= +pki_clone_replicate_schema=True +pki_clone_replication_master_port= +pki_clone_replication_clone_port= +pki_clone_replication_security=None +pki_clone_setup_replication=True +pki_clone_reindex_data=False +pki_master_hostname=%(pki_security_domain_hostname)s +pki_master_https_port=%(pki_security_domain_https_port)s +pki_clone_uri=https://%(pki_master_hostname)s:%(pki_master_https_port)s +pki_enable_access_log=True +pki_enable_java_debugger=False +pki_enable_on_system_boot=True +pki_enable_proxy=False +pki_proxy_http_port=80 +pki_proxy_https_port=443 +pki_security_manager=true +pki_tomcat_server_port=8005 + +pki_http_enable=True + +# Paths +# These are used in the processing of pkispawn and are not supposed +# to be overwritten by user configuration files. +# +pki_systemd_service_create=True + +CATALINA_HOME=/usr/share/tomcat +pki_tomcat_bin_path=%(CATALINA_HOME)s/bin +pki_tomcat_lib_path=%(CATALINA_HOME)s/lib + +############################################################################### +## CA Configuration: ## +## ## +## Values in this section are common to CA subsystems including 'PKI CAs', ## +## 'Cloned CAs', 'Subordinate CAs', and 'External CAs', and contain ## +## required information which MAY be overridden by users as necessary. ## +## ## +## EXTERNAL CAs: To specify an 'External CA', change the value ## +## of 'pki_external' from 'False' to 'True'. ## +## ## +## SUBORDINATE CAs: To specify a 'Subordinate CA', change the value ## +## of 'pki_subordinate' from 'False' to 'True'. ## +## ## +## REMINDER: PKI CA Clones, Subordinate CAs, and External CAs ## +## are MUTUALLY EXCLUSIVE entities!!! ## +############################################################################### +[CA] +pki_ca_signing_key_algorithm=SHA256withRSA +pki_ca_signing_key_size=3072 +pki_ca_signing_key_type=rsa +pki_ca_signing_record_create=True +pki_ca_signing_serial_number=1 +pki_ca_signing_signing_algorithm=SHA256withRSA +pki_ca_signing_subject_dn=cn=CA Signing Certificate,ou=%(pki_instance_name)s,o=%(pki_security_domain_name)s +pki_ca_signing_token= +pki_ca_signing_opFlags= +pki_ca_signing_opFlagsMask= + +# DEPRECATED: Use 'pki_ca_signing_csr_path' instead. +pki_external_csr_path= +pki_ca_signing_csr_path=%(pki_external_csr_path)s + +pki_ocsp_signing_csr_path= +pki_audit_signing_csr_path= +pki_sslserver_csr_path= +pki_subsystem_csr_path= + +pki_ocsp_signing_cert_path= +pki_audit_signing_cert_path= +pki_sslserver_cert_path= +pki_subsystem_cert_path= + +pki_ca_starting_crl_number=0 +pki_external=False +pki_req_ext_add=False +pki_req_ext_oid= +pki_req_ext_critical=False +pki_req_ext_data= +pki_external_step_two=False + +pki_external_pkcs12_path=%(pki_pkcs12_path)s +pki_external_pkcs12_password=%(pki_pkcs12_password)s +pki_import_system_certs=True +pki_import_admin_cert=False + +pki_ocsp_signing_key_algorithm=SHA256withRSA +pki_ocsp_signing_key_size=3072 +pki_ocsp_signing_key_type=rsa +pki_ocsp_signing_nickname=ocspSigningCert cert-%(pki_instance_name)s CA +pki_ocsp_signing_signing_algorithm=SHA256withRSA +pki_ocsp_signing_subject_dn=cn=CA OCSP Signing Certificate,ou=%(pki_instance_name)s,o=%(pki_security_domain_name)s +pki_ocsp_signing_token= +pki_ocsp_signing_opFlags= +pki_ocsp_signing_opFlagsMask= + +pki_profiles_in_ldap=False +pki_random_serial_numbers_enable=False +pki_subordinate=False +pki_subordinate_create_new_security_domain=False +pki_subordinate_security_domain_name=%(pki_dns_domainname)s Subordinate Security Domain + +pki_admin_email=%(pki_admin_name)s@%(pki_dns_domainname)s +pki_admin_name=%(pki_admin_uid)s +pki_admin_nickname=PKI Administrator for %(pki_dns_domainname)s +pki_admin_subject_dn=cn=PKI Administrator,e=%(pki_admin_email)s,ou=%(pki_instance_name)s,o=%(pki_security_domain_name)s +pki_admin_uid=caadmin + +pki_audit_signing_nickname=auditSigningCert cert-%(pki_instance_name)s CA +pki_audit_signing_subject_dn=cn=CA Audit Signing Certificate,ou=%(pki_instance_name)s,o=%(pki_security_domain_name)s + +pki_ds_setup=True +pki_ds_base_dn=o=%(pki_instance_name)s-CA +pki_ds_database=%(pki_instance_name)s-CA +pki_ds_hostname=%(pki_hostname)s + +pki_subsystem_name=CA %(pki_hostname)s %(pki_https_port)s +pki_share_db=False +pki_master_crl_enable=True + +# Default OCSP URI added by AuthInfoAccessExtDefault if the profile +# config is blank. If both are blank, the value is constructed +# based on the CMS hostname and port. +pki_default_ocsp_uri= + +pki_serial_number_range_start= +pki_serial_number_range_end= +pki_request_number_range_start= +pki_request_number_range_end= +pki_replica_number_range_start= +pki_replica_number_range_end= + +# Cert cert ID generator: legacy, random +pki_cert_id_generator=random + +# Cert cert ID length in bits +pki_cert_id_length=128 + +# Cert request ID generator: legacy, random +pki_request_id_generator=random + +# Cert request ID length in bits +pki_request_id_length=128 + +pki_security_domain_setup=True +pki_registry_enable=True + +############################################################################### +## KRA Configuration: ## +## ## +## Values in this section are common to KRA subsystems ## +## including 'PKI KRAs', 'Cloned KRAs', and 'Stand-alone KRAs' and contain ## +## required information which MAY be overridden by users as necessary. ## +## ## +## STAND-ALONE KRAs: To specify a 'Stand-alone KRA', change the value ## +## of 'pki_standalone' from 'False' to 'True', and ## +## specify the various 'pki_external' parameters ## +## as appropriate. ## +## ## +############################################################################### +[KRA] +pki_import_admin_cert=True +pki_standalone=False +pki_kra_ephemeral_requests=False + +# DEPRECATED +# Use 'pki_*_csr_path' instead. +pki_external_admin_csr_path= +pki_external_audit_signing_csr_path= +pki_external_sslserver_csr_path= +pki_external_storage_csr_path= +pki_external_subsystem_csr_path= +pki_external_transport_csr_path= + +pki_admin_csr_path=%(pki_external_admin_csr_path)s +pki_audit_signing_csr_path=%(pki_external_audit_signing_csr_path)s +pki_sslserver_csr_path=%(pki_external_sslserver_csr_path)s +pki_storage_csr_path=%(pki_external_storage_csr_path)s +pki_subsystem_csr_path=%(pki_external_subsystem_csr_path)s +pki_transport_csr_path=%(pki_external_transport_csr_path)s + +pki_external_step_two=False + +# DEPRECATED +# Use 'pki_*_cert_path' instead. +pki_external_admin_cert_path= +pki_external_audit_signing_cert_path= +pki_external_sslserver_cert_path= +pki_external_storage_cert_path= +pki_external_subsystem_cert_path= +pki_external_transport_cert_path= + +pki_admin_cert_path=%(pki_external_admin_cert_path)s +pki_audit_signing_cert_path=%(pki_external_audit_signing_cert_path)s +pki_sslserver_cert_path=%(pki_external_sslserver_cert_path)s +pki_storage_cert_path=%(pki_external_storage_cert_path)s +pki_subsystem_cert_path=%(pki_external_subsystem_cert_path)s +pki_transport_cert_path=%(pki_external_transport_cert_path)s + +pki_storage_key_algorithm=SHA256withRSA +pki_storage_key_size=2048 +pki_storage_key_type=rsa +pki_storage_nickname=storageCert cert-%(pki_instance_name)s KRA +pki_storage_signing_algorithm=SHA256withRSA +pki_storage_subject_dn=cn=DRM Storage Certificate,ou=%(pki_instance_name)s,o=%(pki_security_domain_name)s +pki_storage_token= +pki_storage_opFlags= +pki_storage_opFlagsMask= + +pki_transport_key_algorithm=SHA256withRSA +pki_transport_key_size=2048 +pki_transport_key_type=rsa +pki_transport_nickname=transportCert cert-%(pki_instance_name)s KRA +pki_transport_signing_algorithm=SHA256withRSA +pki_transport_subject_dn=cn=DRM Transport Certificate,ou=%(pki_instance_name)s,o=%(pki_security_domain_name)s +pki_transport_token= +pki_transport_opFlags= +pki_transport_opFlagsMask= + +pki_admin_email=%(pki_admin_name)s@%(pki_dns_domainname)s +pki_admin_name=%(pki_admin_uid)s +pki_admin_nickname=PKI Administrator for %(pki_dns_domainname)s +pki_admin_subject_dn=cn=PKI Administrator,e=%(pki_admin_email)s,ou=%(pki_instance_name)s,o=%(pki_security_domain_name)s +pki_admin_uid=kraadmin + +pki_audit_signing_nickname=auditSigningCert cert-%(pki_instance_name)s KRA +pki_audit_signing_subject_dn=cn=KRA Audit Signing Certificate,ou=%(pki_instance_name)s,o=%(pki_security_domain_name)s + +pki_ds_setup=True +pki_ds_base_dn=o=%(pki_instance_name)s-KRA +pki_ds_database=%(pki_instance_name)s-KRA +pki_ds_hostname=%(pki_hostname)s + +pki_subsystem_name=KRA %(pki_hostname)s %(pki_https_port)s +pki_share_db=True +pki_share_dbuser_dn=uid=pkidbuser,ou=people,%(pki_ds_base_dn)s + +# Key ID generator: legacy, random +pki_key_id_generator=random + +# Key ID length in bits +pki_key_id_length=128 + +# Key request ID generator: legacy, random +pki_request_id_generator=random + +# Key request ID length in bits +pki_request_id_length=128 + +pki_security_domain_setup=True +pki_registry_enable=True + +############################################################################### +## OCSP Configuration: ## +## ## +## Values in this section are common to OCSP subsystems ## +## including 'PKI OCSPs', 'Cloned OCSPs', and 'Stand-alone OCSPs' and ## +## contain required information which MAY be overridden by users as ## +## necessary. ## +## ## +## STAND-ALONE OCSPs: To specify a 'Stand-alone OCSP', change the ## +## value of 'pki_standalone' from 'False' to ## +## 'True', and specify the various 'pki_external' ## +## parameters as appropriate. ## +## (NOTE: Stand-alone OCSP is not yet supported!) ## +## ## +############################################################################### +[OCSP] +pki_import_admin_cert=True +pki_standalone=False + +# DEPRECATED +# Use 'pki_*_csr_path' instead. +pki_external_admin_csr_path= +pki_external_audit_signing_csr_path= +pki_external_signing_csr_path= +pki_external_sslserver_csr_path= +pki_external_subsystem_csr_path= + +pki_admin_csr_path=%(pki_external_admin_csr_path)s +pki_audit_signing_csr_path=%(pki_external_audit_signing_csr_path)s +pki_ocsp_signing_csr_path =%(pki_external_signing_csr_path)s +pki_sslserver_csr_path=%(pki_external_sslserver_csr_path)s +pki_subsystem_csr_path=%(pki_external_subsystem_csr_path)s + +pki_external_step_two=False + +# DEPRECATED +# Use 'pki_*_cert_path' instead. +pki_external_admin_cert_path= +pki_external_audit_signing_cert_path= +pki_external_signing_cert_path= +pki_external_sslserver_cert_path= +pki_external_subsystem_cert_path= + +pki_admin_cert_path=%(pki_external_admin_cert_path)s +pki_audit_signing_cert_path=%(pki_external_audit_signing_cert_path)s +pki_ocsp_signing_cert_path=%(pki_external_signing_cert_path)s +pki_sslserver_cert_path=%(pki_external_sslserver_cert_path)s +pki_subsystem_cert_path=%(pki_external_subsystem_cert_path)s + +pki_ocsp_signing_key_algorithm=SHA256withRSA +pki_ocsp_signing_key_size=3072 +pki_ocsp_signing_key_type=rsa +pki_ocsp_signing_nickname=ocspSigningCert cert-%(pki_instance_name)s OCSP +pki_ocsp_signing_signing_algorithm=SHA256withRSA +pki_ocsp_signing_subject_dn=cn=OCSP Signing Certificate,ou=%(pki_instance_name)s,o=%(pki_security_domain_name)s +pki_ocsp_signing_token= +pki_ocsp_signing_opFlags= +pki_ocsp_signing_opFlagsMask= + +pki_admin_email=%(pki_admin_name)s@%(pki_dns_domainname)s +pki_admin_name=%(pki_admin_uid)s +pki_admin_nickname=PKI Administrator for %(pki_dns_domainname)s +pki_admin_subject_dn=cn=PKI Administrator,e=%(pki_admin_email)s,ou=%(pki_instance_name)s,o=%(pki_security_domain_name)s +pki_admin_uid=ocspadmin + +pki_audit_signing_nickname=auditSigningCert cert-%(pki_instance_name)s OCSP +pki_audit_signing_subject_dn=cn=OCSP Audit Signing Certificate,ou=%(pki_instance_name)s,o=%(pki_security_domain_name)s + +pki_ds_setup=True +pki_ds_base_dn=o=%(pki_instance_name)s-OCSP +pki_ds_database=%(pki_instance_name)s-OCSP +pki_ds_hostname=%(pki_hostname)s + +pki_subsystem_name=OCSP %(pki_hostname)s %(pki_https_port)s +pki_share_db=True +pki_share_dbuser_dn=uid=pkidbuser,ou=people,%(pki_ds_base_dn)s + +pki_security_domain_setup=True +pki_registry_enable=True + +############################################################################### +## TKS Configuration: ## +## ## +## Values in this section are common to TKS subsystems ## +## including 'PKI TKSs' and 'Cloned TKSs', and contain ## +## required information which MAY be overridden by users as necessary. ## +############################################################################### +[TKS] +pki_import_admin_cert=True +pki_admin_email=%(pki_admin_name)s@%(pki_dns_domainname)s +pki_admin_name=%(pki_admin_uid)s +pki_admin_nickname=PKI Administrator for %(pki_dns_domainname)s +pki_admin_subject_dn=cn=PKI Administrator,e=%(pki_admin_email)s,ou=%(pki_instance_name)s,o=%(pki_security_domain_name)s +pki_admin_uid=tksadmin +pki_audit_signing_nickname=auditSigningCert cert-%(pki_instance_name)s TKS +pki_audit_signing_subject_dn=cn=TKS Audit Signing Certificate,ou=%(pki_instance_name)s,o=%(pki_security_domain_name)s + +pki_ds_setup=True +pki_ds_base_dn=o=%(pki_instance_name)s-TKS +pki_ds_database=%(pki_instance_name)s-TKS +pki_ds_hostname=%(pki_hostname)s + +pki_subsystem_name=TKS %(pki_hostname)s %(pki_https_port)s +pki_share_db=True +pki_share_dbuser_dn=uid=pkidbuser,ou=people,%(pki_ds_base_dn)s + +pki_security_domain_setup=True +pki_registry_enable=True + +############################################################################### +## TPS Configuration: ## +## ## +## Values in this section are common to PKI TPS subsystems, and contain ## +## required information which MAY be overridden by users as necessary. ## +############################################################################### +[TPS] +pki_import_admin_cert=True +pki_admin_email=%(pki_admin_name)s@%(pki_dns_domainname)s +pki_admin_name=%(pki_admin_uid)s +pki_admin_nickname=PKI Administrator for %(pki_dns_domainname)s +pki_admin_subject_dn=cn=PKI Administrator,e=%(pki_admin_email)s,ou=%(pki_instance_name)s,o=%(pki_security_domain_name)s +pki_admin_uid=tpsadmin +pki_audit_signing_nickname=auditSigningCert cert-%(pki_instance_name)s TPS +pki_audit_signing_subject_dn=cn=TPS Audit Signing Certificate,ou=%(pki_instance_name)s,o=%(pki_security_domain_name)s + +pki_ds_setup=True +pki_ds_base_dn=o=%(pki_instance_name)s-TPS +pki_ds_database=%(pki_instance_name)s-TPS +pki_ds_hostname=%(pki_hostname)s + +pki_subsystem_name=TPS %(pki_hostname)s %(pki_https_port)s +pki_authdb_hostname=%(pki_hostname)s +pki_authdb_port=389 +pki_authdb_secure_conn=False +pki_ca_uri=https://%(pki_hostname)s:%(pki_https_port)s +pki_kra_uri=https://%(pki_hostname)s:%(pki_https_port)s +pki_tks_uri=https://%(pki_hostname)s:%(pki_https_port)s +pki_enable_server_side_keygen=False +pki_import_shared_secret=False +pki_share_db=True +pki_share_dbuser_dn=uid=pkidbuser,ou=people,%(pki_ds_base_dn)s +pki_source_phone_home_xml=/usr/share/pki/%(pki_subsystem_type)s/conf/phoneHome.xml + +pki_security_domain_setup=True +pki_registry_enable=True + +[ACME] +pki_ds_setup=False +pki_security_domain_setup=False +pki_registry_enable=False + +# Database params: +# - acme_database_type +# - acme_database_url +# - acme_database_auth_type +# - acme_database_bind_dn +# - acme_database_bind_password +# - acme_database_bind_nickname +# - acme_database_user +# - acme_database_password +# - acme_database_base_dn +# +# See /usr/share/pki/acme/database//database.conf + +# Issuer params: +# - acme_issuer_type +# - acme_issuer_url +# - acme_issuer_nickname +# - acme_issuer_extensions +# - acme_issuer_username +# - acme_issuer_password +# - acme_issuer_password_file +# - acme_issuer_profile +# +# See /usr/share/pki/acme/issuer//issuer.conf + +# Realm params: +# - acme_realm_type +# - acme_realm_url +# - acme_realm_auth_type +# - acme_realm_bind_dn +# - acme_realm_bind_password +# - acme_realm_nickname +# - acme_realm_user +# - acme_realm_username +# - acme_realm_password +# - acme_realm_users_dn +# - acme_realm_groups_dn +# +# See /usr/share/pki/acme/realm//realm.conf +[EST] +pki_ds_setup=False +pki_security_domain_setup=False +pki_registry_enable=False +pki_ca_uri=https://%(pki_hostname)s:%(pki_https_port)s +est_ca_profile=estServiceCert +est_ca_user_name= +est_ca_user_password= +est_ca_user_password_file= +est_ca_user_certificate= +est_realm_type= +est_realm_custom= +est_realm_url= +est_realm_auth_type=BasicAuth +est_realm_bind_dn=cn=Directory Manager +est_realm_bind_password= +est_realm_nickname= +est_realm_user= +est_realm_username= +est_realm_password= +est_realm_users_dn=ou=people,dc=est,dc=pki,dc=example,dc=com +est_realm_groups_dn=ou=groups,dc=est,dc=pki,dc=example,dc=com +est_realm_statements=/usr/share/pki/est/conf/realm/statements.conf +est_authorizer_exec_path=/usr/libexec/estauthz diff --git a/base/server/etc/.#default.cfg b/base/server/etc/.#default.cfg new file mode 120000 index 00000000000..0c9351df702 --- /dev/null +++ b/base/server/etc/.#default.cfg @@ -0,0 +1 @@ +mfargetta@fedora.992000:1725871187 \ No newline at end of file diff --git a/base/server/etc/default.cfg b/base/server/etc/default.cfg index 642fb019f19..ff32db7dd5b 100644 --- a/base/server/etc/default.cfg +++ b/base/server/etc/default.cfg @@ -36,8 +36,9 @@ sensitive_parameters= acme_database_bind_password acme_database_password acme_issuer_password - acme_realm_bind_password - acme_realm_password + est_realm_bind_password + est_realm_password + est_ca_password pki_instance_name=pki-tomcat pki_http_port=8080 @@ -663,3 +664,27 @@ pki_registry_enable=False # - acme_realm_groups_dn # # See /usr/share/pki/acme/realm//realm.conf +[EST] +pki_ds_setup=False +pki_security_domain_setup=False +pki_registry_enable=False +pki_ca_uri=https://%(pki_hostname)s:%(pki_https_port)s +est_ca_profile=estServiceCert +est_ca_user_name= +est_ca_user_password= +est_ca_user_password_file= +est_ca_user_certificate=%(pki_subsystem_nickname)s +est_realm_type= +est_realm_custom= +est_realm_url= +est_realm_auth_type=BasicAuth +est_realm_bind_dn=cn=Directory Manager +est_realm_bind_password= +est_realm_nickname= +est_realm_user= +est_realm_username= +est_realm_password= +est_realm_users_dn=ou=people,dc=est,dc=pki,dc=example,dc=com +est_realm_groups_dn=ou=groups,dc=est,dc=pki,dc=example,dc=com +est_realm_statements=/usr/share/pki/est/conf/realm/statements.conf +est_authorizer_exec_path=/usr/libexec/estauthz diff --git a/base/server/examples/installation/est.cfg b/base/server/examples/installation/est.cfg new file mode 100644 index 00000000000..3a64da4694e --- /dev/null +++ b/base/server/examples/installation/est.cfg @@ -0,0 +1,9 @@ +[DEFAULT] +pki_server_database_password=Secret.123 + +[EST] +est_realm_type=ds +est_realm_url=ldap://localhost.localdomain:3389 +est_realm_bind_password=Secret.123 +est_ca_user_name=est-ra-1 +est_ca_user_password=Secret.est diff --git a/base/server/python/pki/server/deployment/#__init__.py# b/base/server/python/pki/server/deployment/#__init__.py# new file mode 100644 index 00000000000..0c85bc47fbe --- /dev/null +++ b/base/server/python/pki/server/deployment/#__init__.py# @@ -0,0 +1,5625 @@ +# Authors: +# Matthew Harmsen +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Copyright (C) 2016 Red Hat, Inc. +# All rights reserved. +# + +from __future__ import absolute_import +import base64 +import binascii +import configparser +import json +import ldap +import logging +import os +import pathlib +import re +import selinux +import shutil +import socket +import struct +import subprocess +import sys +import tempfile +import time +from time import strftime as date +import urllib.parse + +from lxml import etree + +from cryptography import x509 +from cryptography.hazmat.backends import default_backend + +import pki.nssdb +import pki.account +import pki.client +import pki.pkcs12 +import pki.server +import pki.server.deployment.scriptlets.configuration +import pki.server.deployment.scriptlets.fapolicy_setup +import pki.server.deployment.scriptlets.finalization +import pki.server.deployment.scriptlets.infrastructure_layout +import pki.server.deployment.scriptlets.initialization +import pki.server.deployment.scriptlets.instance_layout +import pki.server.deployment.scriptlets.keygen +import pki.server.deployment.scriptlets.security_databases +import pki.server.deployment.scriptlets.selinux_setup +import pki.server.deployment.scriptlets.subsystem_layout +import pki.system +import pki.util + +from . import pkiconfig as config +from . import pkihelper as util +from . import pkimanifest as manifest +from . import pkimessages as log + +seobject = None +if selinux.is_selinux_enabled(): + try: + import seobject + except ImportError: + # TODO: Fedora 22 has an incomplete Python 3 package + # sepolgen is missing. + if sys.version_info.major == 2: + raise + +logger = logging.getLogger(__name__) + + +class PKIDeployer: + """Holds the global dictionaries and the utility objects""" + + def __init__(self): + + # PKI Deployment "Mandatory" Command-Line Variables + self.subsystem_type = None + + # Global dictionary variables + self.mdict = {} + self.main_config = None + self.user_config = None + self.manifest_db = [] + + self.instance = None + self.identity = None + self.namespace = None + self.configuration_file = None + self.directory = None + self.file = None + self.war = None + self.password = None + self.hsm = None + self.certutil = None + self.kra_connector = None + self.systemd = None + self.tps_connector = None + self.nss_db_type = None + + self.with_maven_deps = False + + # Set installation time + ticks = time.time() + self.install_time = time.asctime(time.localtime(ticks)) + + # Generate a timestamp + self.log_timestamp = date('%Y%m%d%H%M%S', time.localtime(ticks)) + self.certificate_timestamp = date('%Y-%m-%d %H:%M:%S', time.localtime(ticks)) + + # Obtain the architecture bit-size + self.architecture = struct.calcsize("P") * 8 + + # Retrieve hostname + self.hostname = socket.getfqdn() + + # Retrieve DNS domainname + self.dns_domainname = subprocess.check_output(["dnsdomainname"]) + self.dns_domainname = self.dns_domainname.decode('ascii').rstrip('\n') + + if not len(self.dns_domainname): + self.dns_domainname = self.hostname + + self.ds_url = None + self.ds_connection = None + self.sd_connection = None + + self.domain_info = None + self.sd_host = None + self.install_token = None + + self.client = None + self.startup_timeout = None + self.request_timeout = None + + self.authdb_url = None + + self.force = False + self.remove_conf = False + self.remove_logs = False + + def set_property(self, key, value, section=None): + + if not section: + section = self.subsystem_type + + if section != "DEFAULT" and not self.main_config.has_section(section): + self.main_config.add_section(section) + + self.main_config.set(section, key, value) + self.flatten_master_dict() + + if section != "DEFAULT" and not self.user_config.has_section(section): + self.user_config.add_section(section) + + self.user_config.set(section, key, value) + + def init(self): + + # Configure startup timeout + try: + self.startup_timeout = int(os.environ['PKISPAWN_STARTUP_TIMEOUT_SECONDS']) + except (KeyError, ValueError): + self.startup_timeout = 120 + + if self.startup_timeout <= 0: + self.startup_timeout = 60 + + # Configure status request timeout. This is used for each + # status request in wait_for_startup(). + value = self.mdict['pki_status_request_timeout'] + if len(value) > 0: + self.request_timeout = int(value) + if self.request_timeout <= 0: + raise ValueError("Request timeout must be greater than zero") + + # Utility objects + self.identity = util.Identity(self) + self.namespace = util.Namespace(self) + self.configuration_file = util.ConfigurationFile(self) + self.directory = util.Directory(self) + self.file = util.File(self) + self.password = util.Password(self) + self.hsm = util.HSM(self) + self.certutil = util.Certutil(self) + self.kra_connector = util.KRAConnector(self) + self.systemd = util.Systemd(self) + self.tps_connector = util.TPSConnector(self) + + self.ds_init() + + if self.subsystem_type == 'TPS': + self.authdb_init() + + def ds_init(self): + + ds_url = self.mdict.get('pki_ds_url') + + if ds_url is None: + + ds_hostname = self.mdict.get('pki_ds_hostname') + + if not ds_hostname: + return + + if config.str2bool(self.mdict['pki_ds_secure_connection']): + ds_protocol = 'ldaps' + ds_port = self.mdict['pki_ds_ldaps_port'] + else: + ds_protocol = 'ldap' + ds_port = self.mdict['pki_ds_ldap_port'] + + ds_url = ds_protocol + '://' + ds_hostname + ':' + ds_port + + self.ds_url = urllib.parse.urlparse(ds_url) + + if self.ds_url.scheme == 'ldaps': + # ldap.set_option(ldap.OPT_DEBUG_LEVEL, 255) + ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, + self.mdict['pki_ds_secure_connection_ca_pem_file']) + ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_DEMAND) + + def authdb_init(self): + + url = self.mdict.get('pki_authdb_url') + + if not url: + + if config.str2bool(self.mdict['pki_authdb_secure_conn']): + scheme = 'ldaps' + else: + scheme = 'ldap' + + hostname = self.mdict['pki_authdb_hostname'] + port = self.mdict['pki_authdb_port'] + + url = scheme + '://' + hostname + ':' + port + + self.authdb_url = urllib.parse.urlparse(url) + + def authdb_base_dn_exists(self): + try: + connection = ldap.initialize(self.authdb_url.geturl()) + results = connection.search_s( + self.mdict['pki_authdb_basedn'], + ldap.SCOPE_BASE) + + if results is None or len(results) == 0: + return False + + return True + + except ldap.NO_SUCH_OBJECT: + return False + + def init_logger(self, filename): + + pki_logger = logging.getLogger('pki') + + # create empty file with the proper permission + pathlib.Path(filename).touch() + os.chmod(filename, pki.server.DEFAULT_FILE_MODE) + + # configure file handler with append mode to preserve the permission + handler = logging.FileHandler(filename) + formatter = logging.Formatter( + '%(asctime)s %(levelname)s: %(message)s', + '%Y-%m-%d %H:%M:%S') + handler.setFormatter(formatter) + + pki_logger.addHandler(handler) + + def validate(self): + # Validate environmental settings for the deployer; + # to be called before self.init(). + + blacklisted_hostnames = ['localhost', 'localhost.localdomain', + 'localhost4', 'localhost4.localdomain4', + 'localhost6', 'localhost6.localdomain6'] + + if self.hostname in blacklisted_hostnames: + raise Exception("This host has a localhost-like domain as its " + + "FQDN. Please change this to a non-localhost " + + "FQDN. Changes must be made in /etc/hosts; to " + + "verify that they have applied run " + + "`python -c 'import socket; print(socket.getfqdn())'`.") + + def flatten_master_dict(self): + + self.mdict.update(__name__="PKI Master Dictionary") + + default_dict = dict(self.main_config.items('DEFAULT')) + default_dict[0] = None + self.mdict.update(default_dict) + + web_server_dict = None + if self.main_config.has_section('Tomcat'): + web_server_dict = dict(self.main_config.items('Tomcat')) + + if web_server_dict: + web_server_dict[0] = None + self.mdict.update(web_server_dict) + + if self.main_config.has_section(self.subsystem_type): + subsystem_dict = dict(self.main_config.items(self.subsystem_type)) + subsystem_dict[0] = None + self.mdict.update(subsystem_dict) + + def configure_server_xml(self): + + server_config = self.instance.get_server_config() + + logger.info('Configuring Tomcat admin port') + server_config.set_port(self.mdict['pki_tomcat_server_port']) + + listener = server_config.get_listener('org.apache.catalina.core.AprLifecycleListener') + if listener is not None: + logger.info('Removing AprLifecycleListener') + # It is not needed since PKI server will use JSS-based SSL connector + server_config.remove_listener('org.apache.catalina.core.AprLifecycleListener') + + listener = server_config.get_listener('com.netscape.cms.tomcat.PKIListener') + if listener is None: + logger.info('Adding PKIListener') + server_config.create_listener('com.netscape.cms.tomcat.PKIListener') + else: + logger.info('Reusing PKIListener') + + server_config.save() + + def configure_http_connectors(self): + + server_config = self.instance.get_server_config() + + # find default HTTP connector + connector = server_config.get_connector(port='8080') + service = connector.getparent() + + # get HTTP connector position + index = service.index(connector) + + if config.str2bool(self.mdict['pki_http_enable']): + + logger.info('Configuring HTTP connector') + connector.set('name', 'Unsecure') + connector.set('port', self.mdict['pki_http_port']) + connector.set('redirectPort', self.mdict['pki_https_port']) + connector.set('maxHttpHeaderSize', '8192') + connector.set('acceptCount', '100') + connector.set('maxThreads', '150') + connector.set('minSpareThreads', '25') + connector.set('enableLookups', 'false') + connector.set('connectionTimeout', '80000') + connector.set('disableUploadTimeout', 'true') + + # add the HTTPS connector after this connector + index = index + 1 + + else: + logger.info('Removing HTTP connector') + service.remove(connector) + + connector = server_config.get_connector(port=self.mdict['pki_https_port']) + + if connector is None: + logger.info('Adding HTTPS connector') + connector = server_config.create_connector(name='Secure', index=index) + else: + logger.info('Updating HTTPS connector') + connector.set('name', 'Secure') + + connector.set('port', self.mdict['pki_https_port']) + connector.set('protocol', 'org.dogtagpki.jss.tomcat.Http11NioProtocol') + connector.set('SSLEnabled', 'true') + connector.set('sslImplementationName', 'org.dogtagpki.jss.tomcat.JSSImplementation') + connector.set('scheme', 'https') + connector.set('secure', 'true') + connector.set('connectionTimeout', '80000') + connector.set('keepAliveTimeout', '300000') + connector.set('maxHttpHeaderSize', '8192') + connector.set('acceptCount', '100') + connector.set('maxThreads', '150') + connector.set('minSpareThreads', '25') + connector.set('enableLookups', 'false') + connector.set('disableUploadTimeout', 'true') + connector.set('enableOCSP', 'false') + connector.set( + 'ocspResponderURL', + 'http://%s:%s/ca/ocsp' % + (self.mdict['pki_hostname'], self.mdict['pki_http_port'])) + connector.set('ocspResponderCertNickname', 'ocspSigningCert cert-pki-ca') + connector.set('ocspCacheSize', '1000') + connector.set('ocspMinCacheEntryDuration', '7200') + connector.set('ocspMaxCacheEntryDuration', '14400') + connector.set('ocspTimeout', '10') + connector.set('passwordFile', self.instance.password_conf) + connector.set('passwordClass', 'org.dogtagpki.jss.tomcat.PlainPasswordFile') + connector.set('certdbDir', self.instance.nssdb_dir) + + sslhost = server_config.get_sslhost(connector) + if sslhost is None: + logger.info('Adding SSL host configuration') + sslhost = server_config.create_sslhost(connector) + else: + logger.info('Updating SSL host configuration') + + sslhost.set('sslProtocol', 'SSL') + sslhost.set('certificateVerification', 'optional') + + sslcert = server_config.get_sslcert(sslhost) + if sslcert is None: + logger.info('Adding SSL certificate configuration') + sslcert = server_config.create_sslcert(sslhost) + else: + logger.info('Updating SSL certificate configuration') + + sslcert.set('certificateKeystoreType', 'pkcs11') + sslcert.set('certificateKeystoreProvider', 'Mozilla-JSS') + sslcert.set('certificateKeyAlias', 'sslserver') + + server_config.save() + + def enable_proxy(self): + + server_config = self.instance.get_server_config() + + logger.info('Adding AJP connector for IPv4') + + connector = etree.Element('Connector') + connector.set('port', self.mdict['pki_ajp_port']) + connector.set('protocol', 'AJP/1.3') + connector.set('redirectPort', self.mdict['pki_https_port']) + connector.set('address', self.mdict['pki_ajp_host_ipv4']) + connector.set('secret', self.mdict['pki_ajp_secret']) + + server_config.add_connector(connector) + + logger.info('Adding AJP connector for IPv6') + + connector = etree.Element('Connector') + connector.set('port', self.mdict['pki_ajp_port']) + connector.set('protocol', 'AJP/1.3') + connector.set('redirectPort', self.mdict['pki_https_port']) + connector.set('address', self.mdict['pki_ajp_host_ipv6']) + connector.set('secret', self.mdict['pki_ajp_secret']) + + server_config.add_connector(connector) + + server_config.save() + + def enable_access_log(self): + + server_config = self.instance.get_server_config() + + valve_class = 'org.apache.catalina.valves.AccessLogValve' + valve = server_config.get_valve(valve_class) + + if valve is None: + logger.info('Adding AccessLogValve') + valve = etree.Element('Valve') + valve.set('className', valve_class) + server_config.add_valve(valve) + else: + logger.info('Updating AccessLogValve') + + valve.set('directory', 'logs') + valve.set('prefix', 'localhost_access_log') + valve.set('suffix', '.txt') + valve.set('pattern', 'common') + + server_config.save() + + def disable_access_log(self): + + server_config = self.instance.get_server_config() + + logger.info('Removing AccessLogValve') + server_config.remove_valve('org.apache.catalina.valves.AccessLogValve') + + server_config.save() + + def update_rsa_pss_algorithm(self, cert_id, alg_type): + + param = 'pki_%s_%s_algorithm' % (cert_id, alg_type) + algorithm = self.mdict[param] + + # if algorithm is XXXwithRSA, change it into XXXwithRSA/PSS + if not re.search(r'withRSA$', algorithm): + return + + self.mdict[param] = '%s/PSS' % algorithm + + def update_rsa_pss_algorithms(self, subsystem): + + logger.info('Updating RSA-PSS algorithms') + + if subsystem.type == 'CA': + self.update_rsa_pss_algorithm('ca_signing', 'key') + self.update_rsa_pss_algorithm('ca_signing', 'signing') + + if subsystem.type == 'KRA': + self.update_rsa_pss_algorithm('storage', 'key') + self.update_rsa_pss_algorithm('storage', 'signing') + + self.update_rsa_pss_algorithm('transport', 'key') + self.update_rsa_pss_algorithm('transport', 'signing') + + if subsystem.type in ['CA', 'OCSP']: + self.update_rsa_pss_algorithm('ocsp_signing', 'key') + self.update_rsa_pss_algorithm('ocsp_signing', 'signing') + + self.update_rsa_pss_algorithm('audit_signing', 'key') + self.update_rsa_pss_algorithm('audit_signing', 'signing') + + self.update_rsa_pss_algorithm('sslserver', 'key') + self.update_rsa_pss_algorithm('sslserver', 'signing') + + self.update_rsa_pss_algorithm('subsystem', 'key') + self.update_rsa_pss_algorithm('subsystem', 'signing') + + self.update_rsa_pss_algorithm('admin', 'key') + + def get_key_params(self, cert_id): + + key_type = self.mdict['pki_%s_key_type' % cert_id] + key_alg = self.mdict['pki_%s_key_algorithm' % cert_id] + key_size = self.mdict['pki_%s_key_size' % cert_id] + + if key_type == 'rsa': + + key_size = int(key_size) + curve = None + + m = re.match(r'(.*)withRSA', key_alg) + if not m: + raise Exception('Invalid key algorithm: %s' % key_alg) + + hash_alg = m.group(1) + + elif key_type == 'ec' or key_type == 'ecc': + + key_type = 'ec' + curve = key_size + key_size = None + + if (cert_id in ['storage', 'transport']): + raise Exception('Invalid key type for KRA %s cert: %s' % (cert_id, key_type)) + + m = re.match(r'(.*)withEC', key_alg) + if not m: + raise Exception('Invalid key algorithm: %s' % key_alg) + + hash_alg = m.group(1) + + else: + raise Exception('Invalid key type: %s' % key_type) + + return (key_type, key_size, curve, hash_alg) + + def update_external_certs_conf(self, external_path): + + external_certs = pki.server.instance.PKIInstance.read_external_certs( + external_path) + + if not external_certs: + return + + # load existing external certs + self.instance.load_external_certs( + os.path.join(self.instance.conf_dir, 'external_certs.conf') + ) + + # add new external certs + for cert in external_certs: + self.instance.add_external_cert(cert.nickname, cert.token) + + def create_server_nssdb(self): + + self.file.modify(self.instance.password_conf) + self.instance.chown(self.instance.password_conf) + + self.instance.makedirs(self.instance.nssdb_dir, exist_ok=True) + + nssdb = self.instance.open_nssdb() + + try: + if not nssdb.exists(): + logger.info('Creating NSS database: %s', self.instance.nssdb_dir) + nssdb.create() + finally: + nssdb.close() + + self.instance.symlink( + self.instance.nssdb_dir, + self.instance.nssdb_link, + exist_ok=True) + + if config.str2bool(self.mdict['pki_hsm_enable']) and \ + self.mdict['pki_hsm_modulename'] and \ + self.mdict['pki_hsm_libfile'] and \ + not nssdb.module_exists(self.mdict['pki_hsm_modulename']): + nssdb.add_module( + self.mdict['pki_hsm_modulename'], + self.mdict['pki_hsm_libfile']) + + # update NSS database owner + self.instance.chown(self.instance.nssdb_dir) + + # update NSS database file permissions + for filename in os.listdir(self.instance.nssdb_dir): + pki.util.chmod( + os.path.join(self.instance.nssdb_dir, filename), + config.PKI_DEPLOYMENT_DEFAULT_SECURITY_DATABASE_PERMISSIONS) + + # update NSS database folder permission + os.chmod( + self.instance.nssdb_dir, + pki.server.DEFAULT_DIR_MODE) + + def remove_server_nssdb(self): + + logger.info('Removing %s', self.instance.nssdb_dir) + pki.util.rmtree(self.instance.nssdb_dir, self.force) + + def import_server_pkcs12(self): + ''' + Import system certificates from PKCS #12 file. + ''' + param = 'pki_server_pkcs12_path' + pki_server_pkcs12_path = self.mdict.get(param) + + if not pki_server_pkcs12_path: + # no PKCS #12 file to import + return + + logger.info('Importing certs and keys from %s', pki_server_pkcs12_path) + + if not os.path.exists(pki_server_pkcs12_path): + raise Exception('Invalid path in %s: %s' % (param, pki_server_pkcs12_path)) + + pki_server_pkcs12_password = self.mdict['pki_server_pkcs12_password'] + if not pki_server_pkcs12_password: + raise Exception('Missing pki_server_pkcs12_password property') + + nssdb = self.instance.open_nssdb() + + try: + nssdb.import_pkcs12( + pkcs12_file=pki_server_pkcs12_path, + pkcs12_password=pki_server_pkcs12_password) + + # update external CA file (if needed) + external_certs_path = self.mdict['pki_server_external_certs_path'] + if not external_certs_path: + return + + self.update_external_certs_conf(external_certs_path) + + finally: + nssdb.close() + + def import_clone_pkcs12(self): + ''' + Import CA certificates from PKCS #12 file for cloning. + ''' + param = 'pki_clone_pkcs12_path' + pki_clone_pkcs12_path = self.mdict.get(param) + + if not pki_clone_pkcs12_path: + # no PKCS #12 file to import + return + + logger.info('Importing certs and keys from %s', pki_clone_pkcs12_path) + + if not os.path.exists(pki_clone_pkcs12_path): + raise Exception('Invalid path in %s: %s' % (param, pki_clone_pkcs12_path)) + + pki_clone_pkcs12_password = self.mdict['pki_clone_pkcs12_password'] + if not pki_clone_pkcs12_password: + raise Exception('Missing pki_clone_pkcs12_password property') + + nssdb = self.instance.open_nssdb() + + try: + # The PKCS12 class requires an NSS database to run. For simplicity + # it uses the NSS database that has just been created. + pkcs12 = pki.pkcs12.PKCS12( + path=pki_clone_pkcs12_path, + password=pki_clone_pkcs12_password, + nssdb=nssdb) + + try: + pkcs12.show_certs() + finally: + pkcs12.close() + + # Import certificates + nssdb.import_pkcs12( + pkcs12_file=pki_clone_pkcs12_path, + pkcs12_password=pki_clone_pkcs12_password) + + print('Certificates in %s:' % self.instance.nssdb_dir) + nssdb.show_certs() + + # Export CA certificate to PEM file; same command as in + # PKIServer.setup_cert_authentication(). + # openssl pkcs12 -in -out /tmp/auth.pem -nodes -nokeys + + pki_ca_crt_path = os.path.join(self.instance.nssdb_dir, 'ca.crt') + + cmd_export_ca = [ + 'openssl', 'pkcs12', + '-in', pki_clone_pkcs12_path, + '-out', pki_ca_crt_path, + '-nodes', + '-nokeys', + '-passin', 'pass:' + pki_clone_pkcs12_password + ] + + res_ca = subprocess.check_output( + cmd_export_ca, + stderr=subprocess.STDOUT).decode('utf-8') + + logger.debug('Result of CA certificate export: %s', res_ca) + + # At this point, we're running as root. However, the subsystem + # will eventually start up as non-root and will attempt to do a + # migration. If we don't fix the permissions now, migration will + # fail and subsystem won't start up. + pki.util.chmod(pki_ca_crt_path, 0o644) + self.instance.chown(pki_ca_crt_path) + + finally: + nssdb.close() + + def install_cert_chain(self): + + param = 'pki_cert_chain_path' + cert_chain_path = self.mdict.get(param) + + if not cert_chain_path: + # no cert chain to import + return + + if not os.path.exists(cert_chain_path): + raise Exception('Certificate chain not found: %s' % cert_chain_path) + + logger.info('Importing cert chain from %s', cert_chain_path) + + destination = os.path.join(self.instance.nssdb_dir, 'ca.crt') + + if os.path.exists(destination): + return + + # When we're passed a CA certificate file and we don't already + # have a CA file for some reason, we need to copy the passed + # file as the root CA certificate to establish trust in the + # Python code. It doesn't import it into the NSS DB for Java + # code, but so far we haven't had any issues with certificate + # validation there. This is only usually necessary when + # installing a non-CA subsystem on a fresh system. + + self.instance.copyfile( + cert_chain_path, + destination, + exist_ok=True) + + def import_ds_ca_cert(self): + + if self.ds_url.scheme != 'ldaps': + return + + nickname = self.mdict['pki_ds_secure_connection_ca_nickname'] + token = self.mdict['pki_self_signed_token'] + cert_file = self.mdict['pki_ds_secure_connection_ca_pem_file'] + trust_attributes = self.mdict['pki_ds_secure_connection_ca_trustargs'] + + nssdb = self.instance.open_nssdb() + + try: + logger.info('Checking DS CA cert: %s', nickname) + # NOTE: ALWAYS use the software DB regardless of whether + # the instance will utilize 'softokn' or an HSM + + cert = nssdb.get_cert(nickname, token=token) + + if cert: + return + + logger.info('Importing DS CA cert') + + nssdb.import_cert_chain( + nickname, + token=token, + cert_chain_file=cert_file, + trust_attributes=trust_attributes) + + finally: + nssdb.close() + + def create_cs_cfg(self, subsystem): + + tmpdir = tempfile.mkdtemp() + + try: + # Copy /usr/share/pki//conf/CS.cfg + # into temporary CS.cfg with param substitution + + source_cs_cfg = os.path.join( + pki.server.PKIServer.SHARE_DIR, + subsystem.name, + 'conf', + 'CS.cfg') + + tmp_cs_cfg = os.path.join(tmpdir, 'CS.cfg') + + self.instance.copyfile( + source_cs_cfg, + tmp_cs_cfg, + params=self.mdict, + exist_ok=True, + force=True) + + # Merge temporary CS.cfg into /var/lib/pki//conf//CS.cfg + # to preserve params in existing CS.cfg + + pki.util.load_properties(tmp_cs_cfg, subsystem.config) + self.instance.store_properties(subsystem.cs_conf, subsystem.config) + + finally: + shutil.rmtree(tmpdir) + + def init_system_cert_params(self, subsystem): + + # Store system cert parameters in installation step to guarantee the + # parameters exist during configuration step and to allow customization. + + certs = subsystem.find_system_certs() + for cert in certs: + + # get CS.cfg tag and pkispawn tag + config_tag = cert['id'] + deploy_tag = config_tag + + if config_tag == 'signing': # for CA and OCSP + deploy_tag = subsystem.name + '_signing' + + # store nickname and tokenname + nickname = self.mdict['pki_%s_nickname' % deploy_tag] + tokenname = self.mdict['pki_%s_token' % deploy_tag] + + if pki.nssdb.internal_token(tokenname): + fullname = nickname + tokenname = pki.nssdb.INTERNAL_TOKEN_NAME + else: + fullname = tokenname + ':' + nickname + + subsystem.set_config('%s.%s.nickname' % (subsystem.name, config_tag), nickname) + subsystem.set_config('%s.%s.tokenname' % (subsystem.name, config_tag), tokenname) + subsystem.set_config('%s.cert.%s.nickname' % (subsystem.name, config_tag), fullname) + + keyalgorithm = self.mdict['pki_%s_key_algorithm' % deploy_tag] + signingalgorithm = self.mdict.get( + 'pki_%s_signing_algorithm' % deploy_tag, keyalgorithm) + + if subsystem.name == 'ca': + if config_tag == 'signing': + subsystem.set_config('ca.signing.defaultSigningAlgorithm', signingalgorithm) + subsystem.set_config('ca.crl.MasterCRL.signingAlgorithm', signingalgorithm) + + elif config_tag == 'ocsp_signing': + subsystem.set_config( + 'ca.ocsp_signing.defaultSigningAlgorithm', + signingalgorithm) + + elif subsystem.name == 'ocsp': + if config_tag == 'signing': + subsystem.set_config('ocsp.signing.defaultSigningAlgorithm', signingalgorithm) + + elif subsystem.name == 'kra': + if config_tag == 'transport': + subsystem.set_config('kra.transportUnit.signingAlgorithm', signingalgorithm) + + # TODO: move more system cert params here + + # If specified in the deployment parameter, add generic CA signing cert + # extension parameters into the CS.cfg. Generic extension for other + # system certs can be added directly into CS.cfg after before the + # configuration step. + + if subsystem.type == 'CA': + + signing_nickname = subsystem.config['ca.signing.nickname'] + subsystem.set_config('ca.signing.certnickname', signing_nickname) + subsystem.set_config('ca.signing.cacertnickname', signing_nickname) + + ocsp_signing_nickname = subsystem.config['ca.ocsp_signing.nickname'] + subsystem.set_config('ca.ocsp_signing.certnickname', ocsp_signing_nickname) + subsystem.set_config('ca.ocsp_signing.cacertnickname', ocsp_signing_nickname) + + if self.configuration_file.add_req_ext: + + subsystem.set_config( + 'preop.cert.signing.ext.oid', + self.configuration_file.req_ext_oid) + subsystem.set_config( + 'preop.cert.signing.ext.data', + self.configuration_file.req_ext_data) + subsystem.set_config( + 'preop.cert.signing.ext.critical', + self.configuration_file.req_ext_critical.lower()) + + if subsystem.type == 'KRA': + + storage_nickname = subsystem.config['kra.storage.nickname'] + storage_token = subsystem.config['kra.storage.tokenname'] + + if pki.nssdb.internal_token(storage_token): + subsystem.set_config('kra.storageUnit.nickName', storage_nickname) + else: + subsystem.set_config('kra.storageUnit.hardware', storage_token) + subsystem.set_config( + 'kra.storageUnit.nickName', + storage_token + ':' + storage_nickname) + + transport_nickname = subsystem.config['kra.transport.nickname'] + transport_token = subsystem.config['kra.transport.tokenname'] + + if pki.nssdb.internal_token(transport_token): + subsystem.set_config('kra.transportUnit.nickName', transport_nickname) + else: + subsystem.set_config( + 'kra.transportUnit.nickName', + transport_token + ':' + transport_nickname) + + if subsystem.type == 'OCSP': + + signing_nickname = subsystem.config['ocsp.signing.nickname'] + subsystem.set_config('ocsp.signing.certnickname', signing_nickname) + subsystem.set_config('ocsp.signing.cacertnickname', signing_nickname) + + audit_nickname = subsystem.config['%s.audit_signing.nickname' % subsystem.name] + audit_token = subsystem.config['%s.audit_signing.tokenname' % subsystem.name] + + if not pki.nssdb.internal_token(audit_token): + audit_nickname = audit_token + ':' + audit_nickname + + subsystem.set_config('log.instance.SignedAudit.signedAuditCertNickname', audit_nickname) + + san_inject = config.str2bool(self.mdict['pki_san_inject']) + logger.info('Injecting SAN: %s', san_inject) + + san_for_server_cert = self.mdict.get('pki_san_for_server_cert') + logger.info('SSL server cert SAN: %s', san_for_server_cert) + + if san_inject and san_for_server_cert: + subsystem.set_config('service.injectSAN', 'true') + subsystem.set_config('service.sslserver.san', san_for_server_cert) + + def init_client_nssdb(self): + + # Place 'slightly' less restrictive permissions on + # the top-level client directory ONLY + + self.directory.create( + self.mdict['pki_client_subsystem_dir'], + uid=0, + gid=0, + perms=config.PKI_DEPLOYMENT_DEFAULT_CLIENT_DIR_PERMISSIONS) + + # Since 'certutil' does NOT strip the 'token=' portion of + # the 'token=password' entries, create a client password file + # which ONLY contains the 'password' for the purposes of + # allowing 'certutil' to generate the security databases + + logger.info('Creating password file: %s', self.mdict['pki_client_password_conf']) + + self.password.create_password_conf( + self.mdict['pki_client_password_conf'], + self.mdict['pki_client_database_password'], + pin_sans_token=True) + + self.file.modify( + self.mdict['pki_client_password_conf'], + uid=0, + gid=0) + + # Similarly, create a simple password file containing the + # PKCS #12 password used when exporting the 'Admin Certificate' + # into a PKCS #12 file + + self.password.create_client_pkcs12_password_conf( + self.mdict['pki_client_pkcs12_password_conf']) + + self.file.modify(self.mdict['pki_client_pkcs12_password_conf']) + + pki.util.makedirs(self.mdict['pki_client_database_dir'], exist_ok=True) + + client_nssdb = pki.nssdb.NSSDatabase( + directory=self.mdict['pki_client_database_dir'], + password_file=self.mdict['pki_client_password_conf'], + user=self.mdict['pki_user'], + group=self.mdict['pki_group']) + + try: + if not client_nssdb.exists(): + client_nssdb.create() + finally: + client_nssdb.close() + + def verify_subsystem_exists(self): + + subsystem_path = os.path.join( + self.instance.base_dir, + self.mdict['pki_subsystem_type']) + + if os.path.exists(subsystem_path): + return + + logger.error( + log.PKI_SUBSYSTEM_DOES_NOT_EXIST_2, + self.subsystem_type, + self.mdict['pki_instance_name']) + + raise Exception( + log.PKI_SUBSYSTEM_DOES_NOT_EXIST_2 % ( + self.subsystem_type, + self.mdict['pki_instance_name'])) + + def get_key_type(self, subsystem, cert_id): + + tag = cert_id + if cert_id == 'signing': # for CA and OCSP + tag = subsystem.name + '_signing' + + key_type = self.mdict['pki_%s_key_type' % tag].upper() + + if key_type == 'ECC': + key_type = 'EC' + + if key_type not in ['RSA', 'EC']: + raise Exception('Unsupported key type: %s' % key_type) + + return key_type + + def get_cert_type(self, subsystem, cert_id): + + if subsystem.type == 'CA': + + if cert_id == 'signing': + if self.configuration_file.subordinate: + # sub CA signing cert is signed remotely by root CA + return 'remote' + + # root CA signing cert is self-signed + return 'selfsign' + + if cert_id == 'sslserver': + if self.configuration_file.clone: + # CA clone SSL server cert is signed remotely by main CA + return 'remote' + + if cert_id == 'subsystem': + if self.mdict['pki_security_domain_type'] == 'existing': + # sub CA subsystem cert is signed remotely by root CA + return 'remote' + + # other CA certs are signed locally by CA itself + return 'local' + + # other subsystem's certs are signed remotely by CA + return 'remote' + + def get_cert_profile(self, subsystem, cert_id): + + if cert_id == 'signing': + + if subsystem.type == 'CA' and self.configuration_file.subordinate: + return 'caInstallCACert' + + elif cert_id == 'sslserver': + + if subsystem.type == 'CA' and self.configuration_file.clone \ + or subsystem.type != 'CA': + key_type = self.get_key_type(subsystem, cert_id) + if key_type == 'RSA': + return 'caInternalAuthServerCert' + elif key_type == 'EC': + return 'caECInternalAuthServerCert' + + elif cert_id == 'subsystem': + + if self.mdict['pki_security_domain_type'] == 'new': + return 'subsystemCert.profile' + + else: # self.mdict['pki_security_domain_type'] == 'existing' + key_type = self.get_key_type(subsystem, cert_id) + if key_type == 'RSA': + return 'caInternalAuthSubsystemCert' + elif key_type == 'EC': + return 'caECInternalAuthSubsystemCert' + + elif cert_id == 'admin': + return 'adminCert.profile' + + # get default profile from CS.cfg + return subsystem.config['preop.cert.%s.profile' % cert_id] + + def init_subsystem(self, subsystem): + + external = self.configuration_file.external + standalone = self.configuration_file.standalone + subordinate = self.configuration_file.subordinate + clone = self.configuration_file.clone + + if config.str2bool(self.mdict['pki_enable_proxy']): + + logger.info('Enabling HTTP proxy') + + subsystem.set_config('proxy.securePort', self.mdict['pki_proxy_https_port']) + subsystem.set_config('proxy.unsecurePort', self.mdict['pki_proxy_http_port']) + + if external or standalone: + + # This is needed by IPA to detect step 1 completion. + # See is_step_one_done() in ipaserver/install/cainstance.py. + + subsystem.set_config('preop.ca.type', 'otherca') + + elif subsystem.type != 'CA' or subordinate: + + subsystem.set_config('preop.ca.type', 'sdca') + + # configure CA + if subsystem.type == 'CA': + if config.str2bool(self.mdict['pki_profiles_in_ldap']): + index = subsystem.get_subsystem_index('profile') + subsystem.set_config( + 'subsystem.%d.class' % index, + 'com.netscape.cmscore.profile.LDAPProfileSubsystem') + + # configure OCSP + if subsystem.type == 'OCSP': + if clone: + subsystem.set_config('ocsp.store.defStore.refreshInSec', '14400') + + # configure TPS + if subsystem.type == 'TPS': + subsystem.set_config( + 'auths.instance.ldap1.ldap.basedn', + self.mdict['pki_authdb_basedn']) + subsystem.set_config( + 'auths.instance.ldap1.ldap.ldapconn.host', + self.authdb_url.hostname) + subsystem.set_config( + 'auths.instance.ldap1.ldap.ldapconn.port', + str(self.authdb_url.port)) + subsystem.set_config( + 'auths.instance.ldap1.ldap.ldapconn.secureConn', + str(self.authdb_url.scheme == 'ldaps').lower()) + subsystem.set_config( + 'auths.instance.ldap1.ldap.ldapauth.clientCertNickname', + self.mdict['pki_subsystem_nickname']) + + def configure_ca(self, subsystem): + + if config.str2bool(self.mdict['pki_use_oaep_rsa_keywrap']): + subsystem.set_config('keyWrap.useOAEP', 'true') + + request_id_generator = self.mdict['pki_request_id_generator'] + + if request_id_generator == 'random': + subsystem.set_config('dbs.request.id.generator', request_id_generator) + subsystem.set_config('dbs.request.id.length', self.mdict['pki_request_id_length']) + + else: # legacy + subsystem.set_config('dbs.beginRequestNumber', '1') + subsystem.set_config('dbs.endRequestNumber', '10000000') + subsystem.set_config('dbs.requestIncrement', '10000000') + subsystem.set_config('dbs.requestLowWaterMark', '2000000') + subsystem.set_config('dbs.requestCloneTransferNumber', '10000') + subsystem.set_config('dbs.requestRangeDN', 'ou=requests,ou=ranges') + + request_number_range_start = self.mdict.get('pki_request_number_range_start') + if request_number_range_start: + subsystem.set_config('dbs.beginRequestNumber', request_number_range_start) + + request_number_range_end = self.mdict.get('pki_request_number_range_end') + if request_number_range_end: + subsystem.set_config('dbs.endRequestNumber', request_number_range_end) + + cert_id_generator = self.mdict['pki_cert_id_generator'] + + if cert_id_generator == 'random': + subsystem.set_config('dbs.cert.id.generator', cert_id_generator) + subsystem.set_config('dbs.cert.id.length', self.mdict['pki_cert_id_length']) + + else: # legacy + subsystem.set_config('dbs.beginSerialNumber', '1') + subsystem.set_config('dbs.endSerialNumber', '10000000') + subsystem.set_config('dbs.serialIncrement', '10000000') + subsystem.set_config('dbs.serialLowWaterMark', '2000000') + subsystem.set_config('dbs.serialCloneTransferNumber', '10000') + subsystem.set_config('dbs.serialRangeDN', 'ou=certificateRepository,ou=ranges') + if config.str2bool(self.mdict['pki_random_serial_numbers_enable']): + subsystem.set_config('dbs.enableRandomSerialNumbers', 'true') + subsystem.set_config('dbs.randomSerialNumberCounter', '0') + + serial_number_range_start = self.mdict.get('pki_serial_number_range_start') + if serial_number_range_start: + subsystem.set_config('dbs.beginSerialNumber', serial_number_range_start) + + serial_number_range_end = self.mdict.get('pki_serial_number_range_end') + if serial_number_range_end: + subsystem.set_config('dbs.endSerialNumber', serial_number_range_end) + + replica_number_range_start = self.mdict.get('pki_replica_number_range_start') + if replica_number_range_start: + subsystem.set_config('dbs.beginReplicaNumber', replica_number_range_start) + + replica_number_range_end = self.mdict.get('pki_replica_number_range_end') + if replica_number_range_end: + subsystem.set_config('dbs.endReplicaNumber', replica_number_range_end) + + ocsp_uri = self.mdict.get('pki_default_ocsp_uri') + if ocsp_uri: + subsystem.set_config('ca.defaultOcspUri', ocsp_uri) + + def configure_kra(self, subsystem): + + if config.str2bool(self.mdict['pki_use_oaep_rsa_keywrap']): + subsystem.set_config('keyWrap.useOAEP', 'true') + + request_id_generator = self.mdict['pki_request_id_generator'] + + if request_id_generator == 'random': + subsystem.set_config('dbs.request.id.generator', request_id_generator) + subsystem.set_config('dbs.request.id.length', self.mdict['pki_request_id_length']) + + else: # legacy + subsystem.set_config('dbs.beginRequestNumber', '1') + subsystem.set_config('dbs.endRequestNumber', '10000000') + subsystem.set_config('dbs.requestIncrement', '10000000') + subsystem.set_config('dbs.requestLowWaterMark', '2000000') + subsystem.set_config('dbs.requestCloneTransferNumber', '10000') + subsystem.set_config('dbs.requestRangeDN', 'ou=requests,ou=ranges') + + key_id_generator = self.mdict['pki_key_id_generator'] + + if key_id_generator == 'random': + subsystem.set_config('dbs.key.id.generator', key_id_generator) + subsystem.set_config('dbs.key.id.length', self.mdict['pki_key_id_length']) + + else: # legacy + subsystem.set_config('dbs.beginSerialNumber', '1') + subsystem.set_config('dbs.endSerialNumber', '10000000') + subsystem.set_config('dbs.serialIncrement', '10000000') + subsystem.set_config('dbs.serialLowWaterMark', '2000000') + subsystem.set_config('dbs.serialCloneTransferNumber', '10000') + subsystem.set_config('dbs.serialRangeDN', 'ou=keyRepository,ou=ranges') + + if config.str2bool(self.mdict['pki_kra_ephemeral_requests']): + logger.debug('Setting ephemeral requests to true') + subsystem.set_config('kra.ephemeralRequests', 'true') + + def add_tps_ca_connector(self, name, ca_url, subsystem, fullname, timestamp): + + subsystem.set_config('tps.connector.%s.enable' % name, 'true') + subsystem.set_config('tps.connector.%s.host' % name, ca_url.hostname) + subsystem.set_config('tps.connector.%s.port' % name, str(ca_url.port)) + subsystem.set_config('tps.connector.%s.minHttpConns' % name, '1') + subsystem.set_config('tps.connector.%s.maxHttpConns' % name, '15') + subsystem.set_config('tps.connector.%s.nickName' % name, fullname) + subsystem.set_config('tps.connector.%s.timeout' % name, '30') + + subsystem.set_config( + 'tps.connector.%s.uri.enrollment' % name, + '/ca/ee/ca/profileSubmitSSLClient') + subsystem.set_config( + 'tps.connector.%s.uri.getcert' % name, + '/ca/ee/ca/displayBySerial') + subsystem.set_config( + 'tps.connector.%s.uri.renewal' % name, + '/ca/ee/ca/profileSubmitSSLClient') + subsystem.set_config( + 'tps.connector.%s.uri.revoke' % name, + '/ca/ee/subsystem/ca/doRevoke') + subsystem.set_config( + 'tps.connector.%s.uri.unrevoke' % name, + '/ca/ee/subsystem/ca/doUnrevoke') + + subsystem.set_config('config.Subsystem_Connections.%s.state' % name, 'Enabled') + subsystem.set_config('config.Subsystem_Connections.%s.timestamp' % name, timestamp) + + cons = subsystem.config.get('target.Subsystem_Connections.list', '').split(',') + if len(cons) == 1 and not cons[0]: + # drop default blank value + cons = [name] + elif name not in cons: + # add new connector + cons.append(name) + subsystem.set_config('target.Subsystem_Connections.list', ','.join(cons)) + + def add_tps_kra_connector(self, name, kra_url, subsystem, fullname, timestamp): + + subsystem.set_config('tps.connector.%s.enable' % name, 'true') + subsystem.set_config('tps.connector.%s.host' % name, kra_url.hostname) + subsystem.set_config('tps.connector.%s.port' % name, str(kra_url.port)) + subsystem.set_config('tps.connector.%s.minHttpConns' % name, '1') + subsystem.set_config('tps.connector.%s.maxHttpConns' % name, '15') + subsystem.set_config('tps.connector.%s.nickName' % name, fullname) + subsystem.set_config('tps.connector.%s.timeout' % name, '30') + + subsystem.set_config( + 'tps.connector.%s.uri.GenerateKeyPair' % name, + '/kra/agent/kra/GenerateKeyPair') + subsystem.set_config( + 'tps.connector.%s.uri.TokenKeyRecovery' % name, + '/kra/agent/kra/TokenKeyRecovery') + + subsystem.set_config('config.Subsystem_Connections.%s.state' % name, 'Enabled') + subsystem.set_config('config.Subsystem_Connections.%s.timestamp' % name, timestamp) + + cons = subsystem.config.get('target.Subsystem_Connections.list', '').split(',') + if len(cons) == 1 and not cons[0]: + # drop default blank value + cons = [name] + elif name not in cons: + # add new connector + cons.append(name) + subsystem.set_config('target.Subsystem_Connections.list', ','.join(cons)) + + def add_tps_tks_connector(self, name, tks_url, subsystem, fullname, timestamp): + + subsystem.set_config('tps.connector.%s.enable' % name, 'true') + subsystem.set_config('tps.connector.%s.host' % name, tks_url.hostname) + subsystem.set_config('tps.connector.%s.port' % name, str(tks_url.port)) + subsystem.set_config('tps.connector.%s.minHttpConns' % name, '1') + subsystem.set_config('tps.connector.%s.maxHttpConns' % name, '15') + subsystem.set_config('tps.connector.%s.nickName' % name, fullname) + subsystem.set_config('tps.connector.%s.timeout' % name, '30') + subsystem.set_config('tps.connector.%s.generateHostChallenge' % name, 'true') + subsystem.set_config('tps.connector.%s.serverKeygen' % name, 'false') + subsystem.set_config('tps.connector.%s.keySet' % name, 'defKeySet') + subsystem.set_config('tps.connector.%s.tksSharedSymKeyName' % name, 'sharedSecret') + + subsystem.set_config( + 'tps.connector.%s.uri.computeRandomData' % name, + '/tks/agent/tks/computeRandomData') + subsystem.set_config( + 'tps.connector.%s.uri.computeSessionKey' % name, + '/tks/agent/tks/computeSessionKey') + subsystem.set_config( + 'tps.connector.%s.uri.createKeySetData' % name, + '/tks/agent/tks/createKeySetData') + subsystem.set_config( + 'tps.connector.%s.uri.encryptData' % name, + '/tks/agent/tks/encryptData') + + subsystem.set_config('config.Subsystem_Connections.%s.state' % name, 'Enabled') + subsystem.set_config('config.Subsystem_Connections.%s.timestamp' % name, timestamp) + + cons = subsystem.config.get('target.Subsystem_Connections.list', '').split(',') + if len(cons) == 1 and not cons[0]: + # drop default blank value + cons = [name] + elif name not in cons: + # add new connector + cons.append(name) + subsystem.set_config('target.Subsystem_Connections.list', ','.join(cons)) + + def configure_tps(self, subsystem): + + baseDN = subsystem.config['internaldb.basedn'] + + subsystem.set_config('tokendb.activityBaseDN', 'ou=Activities,' + baseDN) + subsystem.set_config('tokendb.baseDN', 'ou=Tokens,' + baseDN) + subsystem.set_config('tokendb.certBaseDN', 'ou=Certificates,' + baseDN) + subsystem.set_config('tokendb.userBaseDN', baseDN) + + nickname = subsystem.config['tps.subsystem.nickname'] + token = subsystem.config['tps.subsystem.tokenname'] + + if pki.nssdb.internal_token(token): + fullname = nickname + else: + fullname = token + ':' + nickname + + timestamp = round(time.time() * 1000 * 1000) + + if self.mdict['pki_ca_uri']: + logger.info('Configuring CA connector') + ca_url = urllib.parse.urlparse(self.mdict['pki_ca_uri']) + self.add_tps_ca_connector('ca1', ca_url, subsystem, fullname, timestamp) + + if self.mdict['pki_tks_uri']: + logger.info('Configuring TKS connector') + tks_url = urllib.parse.urlparse(self.mdict['pki_tks_uri']) + self.add_tps_tks_connector('tks1', tks_url, subsystem, fullname, timestamp) + + keygen = config.str2bool(self.mdict['pki_enable_server_side_keygen']) + + if keygen: + if self.mdict['pki_kra_uri']: + logger.info('Configuring KRA connector') + kra_url = urllib.parse.urlparse(self.mdict['pki_kra_uri']) + self.add_tps_kra_connector('kra1', kra_url, subsystem, fullname, timestamp) + + if self.mdict['pki_tks_uri']: + subsystem.set_config('tps.connector.tks1.serverKeygen', 'true') + + # TODO: see if there are other profiles need to be configured + subsystem.set_config( + 'op.enroll.delegateIEtoken.keyGen.encryption.serverKeygen.enable', + 'true') + subsystem.set_config( + 'op.enroll.delegateISEtoken.keyGen.encryption.serverKeygen.enable', + 'true') + subsystem.set_config( + 'op.enroll.externalRegAddToToken.keyGen.encryption.serverKeygen.enable', + 'true') + subsystem.set_config( + 'op.enroll.soKey.keyGen.encryption.serverKeygen.enable', + 'true') + subsystem.set_config( + 'op.enroll.soKeyTemporary.keyGen.encryption.serverKeygen.enable', + 'true') + subsystem.set_config( + 'op.enroll.userKey.keyGen.encryption.serverKeygen.enable', + 'true') + subsystem.set_config( + 'op.enroll.userKeyTemporary.keyGen.encryption.serverKeygen.enable', + 'true') + + else: + # TODO: see if there are other profiles need to be configured + subsystem.set_config( + 'op.enroll.delegateIEtoken.keyGen.encryption.serverKeygen.enable', + 'false') + + subsystem.set_config( + 'op.enroll.delegateISEtoken.keyGen.encryption.serverKeygen.enable', + 'false') + + subsystem.set_config( + 'op.enroll.externalRegAddToToken.keyGen.encryption.serverKeygen.enable', + 'false') + + subsystem.set_config( + 'op.enroll.soKey.keyGen.encryption.serverKeygen.enable', + 'false') + subsystem.set_config( + 'op.enroll.soKey.keyGen.encryption.recovery.destroyed.scheme', + 'GenerateNewKey') + + subsystem.set_config( + 'op.enroll.soKeyTemporary.keyGen.encryption.serverKeygen.enable', + 'false') + subsystem.set_config( + 'op.enroll.soKeyTemporary.keyGen.encryption.recovery.onHold.scheme', + 'GenerateNewKey') + + subsystem.set_config( + 'op.enroll.userKey.keyGen.encryption.serverKeygen.enable', + 'false') + subsystem.set_config( + 'op.enroll.userKey.keyGen.encryption.recovery.destroyed.scheme', + 'GenerateNewKey') + + subsystem.set_config( + 'op.enroll.userKeyTemporary.keyGen.encryption.serverKeygen.enable', + 'false') + subsystem.set_config( + 'op.enroll.userKeyTemporary.keyGen.encryption.recovery.onHold.scheme', + 'GenerateNewKey') + + def configure_internal_database(self, subsystem): + + if self.ds_url.scheme == 'ldaps': + subsystem.set_config('internaldb.ldapconn.secureConn', 'true') + + elif self.ds_url.scheme == 'ldap': + subsystem.set_config('internaldb.ldapconn.secureConn', 'false') + + else: + raise Exception('Unsupported protocol: %s' % self.ds_url.scheme) + + subsystem.set_config('internaldb.ldapconn.host', self.ds_url.hostname) + subsystem.set_config('internaldb.ldapconn.port', self.ds_url.port) + + subsystem.set_config('internaldb.ldapauth.bindDN', self.mdict['pki_ds_bind_dn']) + subsystem.set_config('internaldb.basedn', self.mdict['pki_ds_base_dn']) + subsystem.set_config('internaldb.database', self.mdict['pki_ds_database']) + + def configure_subsystem(self, subsystem): + + if subsystem.type == 'CA': + self.configure_ca(subsystem) + + if subsystem.type == 'KRA': + self.configure_kra(subsystem) + + if subsystem.type == 'TPS': + self.configure_tps(subsystem) + + def request_ranges(self, subsystem): + + if subsystem.type not in ['CA', 'KRA']: + return + + master_url = self.mdict['pki_clone_uri'] + + logger.info('Requesting ranges from %s master', subsystem.type) + subsystem.request_ranges(master_url, session_id=self.install_token.token) + + def import_master_config(self, subsystem): + + master_url = self.mdict['pki_clone_uri'] + + logger.info('Retrieving config params from %s master', subsystem.type) + + names = [] + substores = [] + + if config.str2bool(self.mdict['pki_ds_setup']): + + names.extend([ + 'internaldb.ldapauth.password', + 'internaldb.replication.password' + ]) + + substores.extend([ + 'internaldb', + 'internaldb.ldapauth', + 'internaldb.ldapconn' + ]) + + tags = subsystem.config['preop.cert.list'].split(',') + for tag in tags: + if tag == 'sslserver': + continue + + # check CSR in CS.cfg + cert_id = self.get_cert_id(subsystem, tag) + param = 'pki_%s_csr_path' % cert_id + + if self.mdict.get(param): + # CSR already exists + continue + + # CSR doesn't provided, import from master + names.append('%s.%s.certreq' % (subsystem.name, tag)) + + if subsystem.name == 'ca': + substores.append('ca.connector.KRA') + else: + names.append('cloning.ca.type') + + master_config = subsystem.retrieve_config( + master_url, + names, + substores, + session_id=self.install_token.token) + + master_properties = master_config['Properties'] + + if config.str2bool(self.mdict['pki_ds_setup']): + + logger.info('Validating %s database config params', subsystem.type) + + master_hostname = master_properties['internaldb.ldapconn.host'] + master_port = master_properties['internaldb.ldapconn.port'] + + replica_hostname = subsystem.config['internaldb.ldapconn.host'] + replica_port = subsystem.config['internaldb.ldapconn.port'] + + if master_hostname == replica_hostname and master_port == replica_port: + raise Exception('%s database already set up' % subsystem.type) + + logger.info('Importing %s master config params', subsystem.type) + + requests = {key: val for key, val in master_properties.items() if key.endswith('.certreq')} + for key, value in requests.items(): + self.store_master_cert_request(subsystem, key, value) + master_properties.pop(key) + + subsystem.import_master_config(master_properties) + return master_config + + def store_master_cert_request(self, subsystem, key, csr): + + csr_pem = pki.nssdb.convert_csr(csr, 'base64', 'pem') + tag = key.split('.')[1] + csr_path = subsystem.csr_file(tag) + + self.file.create(csr_path) + with open(csr_path, 'w', encoding='utf-8') as f: + f.write(csr_pem) + + def setup_database(self, subsystem, master_config): + + if config.str2bool(self.mdict['pki_ds_remove_data']): + + if config.str2bool(self.mdict['pki_ds_create_new_db']): + logger.info('Removing existing database') + subsystem.remove_database(force=True) + + elif not config.str2bool(self.mdict['pki_clone']) or \ + config.str2bool(self.mdict['pki_clone_setup_replication']): + logger.info('Emptying existing database') + subsystem.empty_database(force=True) + + else: + logger.info('Reusing replicated database') + + if config.str2bool(self.mdict['pki_ds_create_new_db']): + logger.info('Creating database') + subsystem.create_database() + + logger.info('Initializing database') + + # In most cases, we want to replicate the schema and therefore not add it here. + # We provide this option though in case the clone already has schema + # and we want to replicate back to the master. + + # On the other hand, if we are not setting up replication, + # then we are assuming that replication is already taken care of, + # and schema has already been replicated. + + skip_schema = config.str2bool(self.mdict['pki_clone']) and \ + config.str2bool(self.mdict['pki_clone_setup_replication']) and \ + config.str2bool(self.mdict['pki_clone_replicate_schema']) + + # When cloning a subsystem without setting up the replication agreements, + # the database is a subtree of an existing tree and is already replicated, + # so there is no need to set up the base entry. + + skip_base = not config.str2bool(self.mdict['pki_ds_create_new_db']) and \ + config.str2bool(self.mdict['pki_clone']) and \ + not config.str2bool(self.mdict['pki_clone_setup_replication']) + + skip_containers = config.str2bool(self.mdict['pki_clone']) + + subsystem.init_database( + skip_schema=skip_schema, + skip_base=skip_base, + skip_containers=skip_containers) + + if config.str2bool(self.mdict['pki_clone']) and \ + config.str2bool(self.mdict['pki_clone_setup_replication']): + self.setup_replication(subsystem, master_config) + + # For security a PKI subsystem can be configured to use a database user + # that only has a limited access to the database (instead of cn=Directory + # Manager that has a full access to the database). + # + # The default database user is uid=pkidbuser,ou=people,. + # However, if the subsystem is configured to share the database with another + # subsystem (pki_share_db=True), it can also be configured to use the same + # database user (pki_share_dbuser_dn). + + if config.str2bool(self.mdict['pki_share_db']): + dbuser = self.mdict['pki_share_dbuser_dn'] + else: + dbuser = 'uid=pkidbuser,ou=people,' + self.mdict['pki_ds_base_dn'] + + subsystem.grant_database_access(dbuser) + + # Always create search and VLV indexes since they will not be replicated + subsystem.add_indexes() + + # If the database is already replicated but not yet indexed, rebuild the indexes + if config.str2bool(self.mdict['pki_clone']) and \ + not config.str2bool(self.mdict['pki_clone_setup_replication']) and \ + config.str2bool(self.mdict['pki_clone_reindex_data']): + subsystem.rebuild_indexes() + + subsystem.add_vlv() + subsystem.reindex_vlv() + + def setup_replication(self, subsystem, master_config): + + logger.info('Setting up replication') + + master_replication_port = self.mdict['pki_clone_replication_master_port'] + logger.info('- master replication port: %s', master_replication_port) + + replica_replication_port = self.mdict['pki_clone_replication_clone_port'] + logger.info('- replica replication port: %s', replica_replication_port) + + ds_port = subsystem.config['internaldb.ldapconn.port'] + logger.info('- internaldb.ldapconn.port: %s', ds_port) + + secure_conn = subsystem.config['internaldb.ldapconn.secureConn'] + logger.info('- internaldb.ldapconn.secureConn: %s', secure_conn) + + if replica_replication_port == ds_port and secure_conn == 'true': + replication_security = 'SSL' + + else: + replication_security = self.mdict['pki_clone_replication_security'] + if not replication_security: + replication_security = 'None' + + logger.info('- replication security: %s', replication_security) + + # get master database config + + master_ldap_config = {} + for name in master_config['Properties']: + + match = re.match(r'internaldb\.(.*)$', name) + + if not match: + continue + + new_name = match.group(1) # strip internaldb prefix + + if new_name == 'replication.password': # unused + continue + + elif new_name == 'ldapauth.bindPWPrompt': # unused + continue + + elif new_name.startswith('_'): # ignore comments + continue + + elif new_name == 'ldapauth.password': # rename + new_name = 'ldapauth.bindPassword' + + value = master_config['Properties'][name] + + master_ldap_config[new_name] = value + + # get replica database config + + replica_ldap_config = {} + for name in subsystem.config: + + match = re.match(r'internaldb\.(.*)$', name) + + if not match: + continue + + new_name = match.group(1) # strip internaldb prefix + + if new_name.startswith('_'): # ignore comments + continue + + elif new_name == 'ldapauth.bindPWPrompt': # replace + new_name = 'ldapauth.bindPassword' + value = self.instance.get_password('internaldb') + + else: + value = subsystem.config[name] + + replica_ldap_config[new_name] = value + + hostname = self.mdict['pki_hostname'] + master_agreement_name = 'masterAgreement1-%s-%s' % (hostname, self.instance.name) + replica_agreement_name = 'cloneAgreement1-%s-%s' % (hostname, self.instance.name) + + master_hostname = master_ldap_config['ldapconn.host'] + if not master_replication_port: + master_replication_port = master_ldap_config['ldapconn.port'] + master_url = 'ldap://%s:%s' % (master_hostname, master_replication_port) + + master_bind_dn = 'cn=Replication Manager %s,ou=csusers,cn=config' % \ + master_agreement_name + master_bind_password = master_config['Properties']['internaldb.replication.password'] + + replica_hostname = replica_ldap_config['ldapconn.host'] + if not replica_replication_port: + replica_replication_port = ds_port + replica_url = 'ldap://%s:%s' % (replica_hostname, replica_replication_port) + + replica_bind_dn = 'cn=Replication Manager %s,ou=csusers,cn=config' % \ + replica_agreement_name + replica_bind_password = self.instance.get_password('replicationdb') + + logger.info('Enable replication on master') + + # TODO: provide param to specify the replica ID for the master + subsystem.enable_replication( + master_ldap_config, + master_bind_dn, + master_bind_password, + None) + + logger.info('Enable replication on replica') + + # TODO: provide param to specify the replica ID for the replica + subsystem.enable_replication( + replica_ldap_config, + replica_bind_dn, + replica_bind_password, + None) + + logger.info('Adding master replication agreement') + logger.info('- replica URL: %s', replica_url) + + subsystem.add_replication_agreement( + master_agreement_name, + master_ldap_config, + replica_url, + replica_bind_dn, + replica_bind_password, + replication_security) + + logger.info('Adding replica replication agreement') + logger.info('- master URL: %s', master_url) + + subsystem.add_replication_agreement( + replica_agreement_name, + replica_ldap_config, + master_url, + master_bind_dn, + master_bind_password, + replication_security) + + logger.info('Initializing replication agreement') + + subsystem.init_replication_agreement( + master_agreement_name, + master_ldap_config) + + def is_using_legacy_id_generator(self, subsystem): + + if subsystem.type in ['CA', 'KRA']: + + request_id_generator = subsystem.config.get('dbs.request.id.generator', 'legacy') + logger.info('Request ID generator: %s', request_id_generator) + + if request_id_generator == 'legacy': + return True + + if subsystem.type == 'CA': + + cert_id_generator = subsystem.config.get('dbs.cert.id.generator', 'legacy') + logger.info('Certificate ID generator: %s', cert_id_generator) + + if cert_id_generator == 'legacy': + return True + + elif subsystem.type == 'KRA': + + key_id_generator = subsystem.config.get('dbs.key.id.generator', 'legacy') + logger.info('Key ID generator: %s', key_id_generator) + + if key_id_generator == 'legacy': + return True + + return False + + def get_cert_id(self, subsystem, tag): + + if tag == 'signing': + return '%s_%s' % (subsystem.name, tag) + else: + return tag + + def generate_ca_signing_request(self, subsystem): + + csr_path = self.mdict.get('pki_ca_signing_csr_path') + if not csr_path: + return + + basic_constraints_ext = { + 'ca': True, + 'path_length': None, + 'critical': True + } + + key_usage_ext = { + 'digitalSignature': True, + 'nonRepudiation': True, + 'certSigning': True, + 'crlSigning': True, + 'critical': True + } + + # if specified, add generic CSR extension + generic_exts = None + + if 'preop.cert.signing.ext.oid' in subsystem.config and \ + 'preop.cert.signing.ext.data' in subsystem.config: + + data = subsystem.config['preop.cert.signing.ext.data'] + critical = subsystem.config['preop.cert.signing.ext.critical'] + + generic_ext = { + 'oid': subsystem.config['preop.cert.signing.ext.oid'], + 'data': binascii.unhexlify(data), + 'critical': config.str2bool(critical) + } + + generic_exts = [generic_ext] + + tag = 'signing' + cert = subsystem.get_subsystem_cert(tag) + token = pki.nssdb.normalize_token(cert['token']) + + if not token: + token = self.mdict['pki_token_name'] + + nssdb = self.instance.open_nssdb(token) + + try: + self.generate_csr( + nssdb, + subsystem, + tag, + csr_path, + basic_constraints_ext=basic_constraints_ext, + key_usage_ext=key_usage_ext, + generic_exts=generic_exts, + subject_key_id=self.configuration_file.req_ski, + ) + + finally: + nssdb.close() + + def generate_kra_storage_request(self, subsystem): + + csr_path = self.mdict.get('pki_storage_csr_path') + if not csr_path: + return + + key_usage_ext = { + 'digitalSignature': True, + 'nonRepudiation': True, + 'keyEncipherment': True, + 'dataEncipherment': True, + 'critical': True + } + + extended_key_usage_ext = { + 'clientAuth': True + } + + tag = 'storage' + cert = subsystem.get_subsystem_cert(tag) + token = pki.nssdb.normalize_token(cert['token']) + + if not token: + token = self.mdict['pki_token_name'] + + nssdb = self.instance.open_nssdb(token) + + try: + self.generate_csr( + nssdb, + subsystem, + tag, + csr_path, + key_usage_ext=key_usage_ext, + extended_key_usage_ext=extended_key_usage_ext + ) + + finally: + nssdb.close() + + def generate_kra_transport_request(self, subsystem): + + csr_path = self.mdict.get('pki_transport_csr_path') + if not csr_path: + return + + key_usage_ext = { + 'digitalSignature': True, + 'nonRepudiation': True, + 'keyEncipherment': True, + 'dataEncipherment': True, + 'critical': True + } + + extended_key_usage_ext = { + 'clientAuth': True + } + + tag = 'transport' + cert = subsystem.get_subsystem_cert(tag) + token = pki.nssdb.normalize_token(cert['token']) + + if not token: + token = self.mdict['pki_token_name'] + + nssdb = self.instance.open_nssdb(token) + + try: + self.generate_csr( + nssdb, + subsystem, + tag, + csr_path, + key_usage_ext=key_usage_ext, + extended_key_usage_ext=extended_key_usage_ext + ) + + finally: + nssdb.close() + + def generate_ocsp_signing_request(self, subsystem): + + csr_path = self.mdict.get('pki_ocsp_signing_csr_path') + if not csr_path: + return + + tag = 'signing' + cert = subsystem.get_subsystem_cert(tag) + token = pki.nssdb.normalize_token(cert['token']) + + if not token: + token = self.mdict['pki_token_name'] + + nssdb = self.instance.open_nssdb(token) + + try: + self.generate_csr( + nssdb, + subsystem, + tag, + csr_path + ) + + finally: + nssdb.close() + + def generate_sslserver_request(self, subsystem): + + csr_path = self.mdict.get('pki_sslserver_csr_path') + if not csr_path: + return + + key_usage_ext = { + 'digitalSignature': True, + 'nonRepudiation': True, + 'keyEncipherment': True, + 'dataEncipherment': True, + 'critical': True + } + + extended_key_usage_ext = { + 'serverAuth': True + } + + tag = 'sslserver' + cert = subsystem.get_subsystem_cert(tag) + token = pki.nssdb.normalize_token(cert['token']) + + if not token: + token = self.mdict['pki_token_name'] + + nssdb = self.instance.open_nssdb(token) + + try: + self.generate_csr( + nssdb, + subsystem, + tag, + csr_path, + key_usage_ext=key_usage_ext, + extended_key_usage_ext=extended_key_usage_ext + ) + + finally: + nssdb.close() + + def generate_subsystem_request(self, subsystem): + + csr_path = self.mdict.get('pki_subsystem_csr_path') + if not csr_path: + return + + key_usage_ext = { + 'digitalSignature': True, + 'nonRepudiation': True, + 'keyEncipherment': True, + 'dataEncipherment': True, + 'critical': True + } + + extended_key_usage_ext = { + 'serverAuth': True, + 'clientAuth': True + } + + tag = 'subsystem' + cert = subsystem.get_subsystem_cert(tag) + token = pki.nssdb.normalize_token(cert['token']) + + if not token: + token = self.mdict['pki_token_name'] + + nssdb = self.instance.open_nssdb(token) + + try: + self.generate_csr( + nssdb, + subsystem, + tag, + csr_path, + key_usage_ext=key_usage_ext, + extended_key_usage_ext=extended_key_usage_ext + ) + + finally: + nssdb.close() + + def generate_audit_signing_request(self, subsystem): + + csr_path = self.mdict.get('pki_audit_signing_csr_path') + if not csr_path: + return + + key_usage_ext = { + 'digitalSignature': True, + 'nonRepudiation': True, + 'critical': True + } + + tag = 'audit_signing' + cert = subsystem.get_subsystem_cert(tag) + token = pki.nssdb.normalize_token(cert['token']) + + if not token: + token = self.mdict['pki_token_name'] + + nssdb = self.instance.open_nssdb(token) + + try: + self.generate_csr( + nssdb, + subsystem, + tag, + csr_path, + key_usage_ext=key_usage_ext + ) + + finally: + nssdb.close() + + def generate_admin_request(self, subsystem): + + csr_path = self.mdict.get('pki_admin_csr_path') + if not csr_path: + return + + client_nssdb = pki.nssdb.NSSDatabase( + directory=self.mdict['pki_client_database_dir'], + password_file=self.mdict['pki_client_password_conf']) + + try: + self.generate_csr( + client_nssdb, + subsystem, + 'admin', + csr_path + ) + + finally: + client_nssdb.close() + + def generate_system_cert_requests(self, subsystem): + + if subsystem.name == 'ca': + self.generate_ca_signing_request(subsystem) + + if subsystem.name == 'kra': + self.generate_kra_storage_request(subsystem) + self.generate_kra_transport_request(subsystem) + + if subsystem.name == 'ocsp': + self.generate_ocsp_signing_request(subsystem) + + if subsystem.name in ['kra', 'ocsp', 'tks', 'tps']: + self.generate_sslserver_request(subsystem) + self.generate_subsystem_request(subsystem) + self.generate_audit_signing_request(subsystem) + self.generate_admin_request(subsystem) + + def import_system_cert_request(self, subsystem, tag): + + cert_id = self.get_cert_id(subsystem, tag) + param = 'pki_%s_csr_path' % cert_id + source_path = self.mdict.get(param) + + if not source_path: + # no CSR file to import + return + + logger.info('Importing CSR for %s from %s', tag, source_path) + + if not os.path.exists(source_path): + raise Exception('Invalid path in %s: %s' % (param, source_path)) + + dest_path = subsystem.csr_file(tag) + + if os.path.realpath(source_path) == os.path.realpath(dest_path): + # CSR already imported + return + + self.file.copy( + old_name=source_path, + new_name=dest_path, + overwrite_flag=True) + self.instance.chown(dest_path) + + def import_system_cert_requests(self, subsystem): + + if subsystem.name == 'ca': + self.import_system_cert_request(subsystem, 'signing') + self.import_system_cert_request(subsystem, 'ocsp_signing') + + if subsystem.name == 'kra': + self.import_system_cert_request(subsystem, 'storage') + self.import_system_cert_request(subsystem, 'transport') + + if subsystem.name == 'ocsp': + self.import_system_cert_request(subsystem, 'signing') + + self.import_system_cert_request(subsystem, 'audit_signing') + self.import_system_cert_request(subsystem, 'subsystem') + self.import_system_cert_request(subsystem, 'sslserver') + + def import_ca_signing_cert(self, nssdb): + param = 'pki_ca_signing_cert_path' + cert_file = self.mdict.get(param) + + if not cert_file: + # no CA signing cert file to import + return + + logger.info('Importing CA signing cert from %s', cert_file) + + if not os.path.exists(cert_file): + raise Exception('Invalid path in %s: %s' % (param, cert_file)) + + nickname = self.mdict['pki_ca_signing_nickname'] + + logger.info('Importing ca_signing certificate from %s', cert_file) + + nssdb.import_cert_chain( + nickname=nickname, + cert_chain_file=cert_file, + trust_attributes='CT,C,C') + + def import_system_cert( + self, + nssdb, + subsystem, + tag, + trust_attributes=None): + + logger.debug('import_system_cert') + + cert_id = self.get_cert_id(subsystem, tag) + param = 'pki_%s_cert_path' % cert_id + cert_file = self.mdict.get(param) + + if not cert_file: + # no system cert to import + return + + logger.info('Importing %s cert from %s', cert_id, cert_file) + + if not os.path.exists(cert_file): + raise Exception('Invalid path in %s: %s' % (param, cert_file)) + + cert = subsystem.get_subsystem_cert(tag) + nickname = cert['nickname'] + token = pki.nssdb.normalize_token(cert['token']) + + if not token: + token = self.mdict.get('pki_sslserver_token') + if not token: + token = self.mdict['pki_token_name'] + + nssdb.import_cert_chain( + nickname=nickname, + cert_chain_file=cert_file, + token=token, + trust_attributes=trust_attributes) + + def load_admin_cert(self): + + param = 'pki_admin_cert_path' + cert_file = self.mdict.get(param) + + if not cert_file: + # no admin cert to load + return None + + if not os.path.exists(cert_file): + raise Exception('Invalid path in %s: %s' % (param, cert_file)) + + logger.info('Loading admin cert from %s', cert_file) + + with open(cert_file, 'r', encoding='utf-8') as f: + return f.read() + + def import_admin_cert(self, cert_data): + + nickname = self.mdict['pki_admin_nickname'] + + client_nssdb = pki.nssdb.NSSDatabase( + directory=self.mdict['pki_client_database_dir'], + password_file=self.mdict['pki_client_password_conf']) + + try: + logger.info('Importing admin cert into %s', client_nssdb.directory) + + client_nssdb.import_cert_chain( + nickname=nickname, + cert_chain_data=cert_data, + trust_attributes=',,') + + finally: + client_nssdb.close() + + def store_admin_cert(self, cert_data): + + cert_file = self.mdict['pki_client_admin_cert'] + + if os.path.exists(cert_file): + logger.info('Admin cert already exists in %s', cert_file) + return None + + logger.info('Storing admin cert into %s', cert_file) + + with open(cert_file, 'w', encoding='utf-8') as f: + f.write(cert_data) + + os.chmod(cert_file, pki.server.DEFAULT_FILE_MODE) + + def export_admin_pkcs12(self): + + pkcs12_file = self.mdict['pki_client_admin_cert_p12'] + + if os.path.exists(pkcs12_file): + logger.info('Admin cert already exists in %s', pkcs12_file) + return + + pkcs12_path = os.path.abspath(pkcs12_file) + pkcs12_dir = os.path.dirname(pkcs12_path) + + # Create directory for PKCS #12 file + self.directory.create(pkcs12_dir) + + nickname = self.mdict['pki_admin_nickname'] + + client_nssdb = pki.nssdb.NSSDatabase( + directory=self.mdict['pki_client_database_dir'], + password_file=self.mdict['pki_client_password_conf']) + + try: + cert = client_nssdb.get_cert(nickname) + + if not cert: + logger.info('Admin cert does not exist in %s', client_nssdb.directory) + return + + logger.info('Exporting admin cert into %s', pkcs12_file) + + pkcs12_password_file = self.mdict['pki_client_pkcs12_password_conf'] + + client_nssdb.export_pkcs12( + pkcs12_file, + pkcs12_password_file=pkcs12_password_file, + nicknames=[nickname], + include_chain=False) + + finally: + client_nssdb.close() + + os.chmod( + pkcs12_file, + config.PKI_DEPLOYMENT_DEFAULT_SECURITY_DATABASE_PERMISSIONS) + + def import_certs_and_keys(self, nssdb): + + param = 'pki_external_pkcs12_path' + pkcs12_file = self.mdict.get(param) + + if not pkcs12_file: + # no PKCS #12 file to import + return + + logger.info('Importing certs and keys from %s', pkcs12_file) + + if not os.path.exists(pkcs12_file): + raise Exception('Invalid path in %s: %s' % (param, pkcs12_file)) + + pkcs12_password = self.mdict['pki_external_pkcs12_password'] + nssdb.import_pkcs12(pkcs12_file, pkcs12_password) + + def import_cert_chain(self, nssdb, subsystem): + + logger.debug('PKIDeployer.import_cert_chain()') + + subordinate = self.configuration_file.subordinate + clone = self.configuration_file.clone + + cert_chain = None + + cert_chain_path = self.mdict.get('pki_cert_chain_path') + if cert_chain_path: + + # Load the cert chain from file specified in pki_cert_chain_path. + + if not os.path.exists(cert_chain_path): + raise Exception('Certificate chain not found: %s' % cert_chain_path) + + logger.info('Loading cert chain from %s', cert_chain_path) + with open(cert_chain_path, 'r', encoding='utf-8') as f: + cert_chain = f.read() + + elif (subsystem.type == 'CA' and subordinate or subsystem.type != 'CA') \ + and not clone \ + and not self.mdict['pki_server_pkcs12_path']: + + # For primary (not clone) sub-CA and KRA, OCSP, TKS, and TPS, + # retrieve the cert chain from the issuing CA unless it's already + # imported from PKCS #12 file specified in pki_server_pkcs12_path. + + url = self.mdict['pki_issuing_ca'] + logger.info('Retrieving cert chain from %s', url) + cert_chain = self.get_ca_signing_cert(url) + + elif subsystem.type == 'CA' \ + and clone \ + and not self.mdict['pki_clone_pkcs12_path']: + + # For root CA and sub-CA clone, retrieve the cert chain from the + # primary server unless it's already imported from a PKCS #12 file + # specified in pki_clone_pkcs12_path. + + url = self.mdict['pki_clone_uri'] + logger.info('Retrieving cert chain from %s', url) + cert_chain = self.get_ca_signing_cert(url) + + if not cert_chain: + return + + nickname = self.mdict['pki_cert_chain_nickname'] + logger.info('Importing cert chain as %s', nickname) + logger.debug('- cert chain:\n%s', cert_chain) + + nssdb.import_cert_chain( + nickname=nickname, + cert_chain_data=cert_chain, + trust_attributes='CT,C,C') + + def import_system_certs(self, nssdb, subsystem): + + logger.debug("import_system_certs") + + if subsystem.name == 'ca': + self.import_system_cert(nssdb, subsystem, 'signing', 'CT,C,C') + self.import_system_cert(nssdb, subsystem, 'ocsp_signing') + + if subsystem.name == 'kra': + self.import_ca_signing_cert(nssdb) + + self.import_system_cert(nssdb, subsystem, 'storage') + self.import_system_cert(nssdb, subsystem, 'transport') + + admin_cert = self.load_admin_cert() + if admin_cert: + self.import_admin_cert(admin_cert) + + if subsystem.name == 'ocsp': + self.import_ca_signing_cert(nssdb) + + self.import_system_cert(nssdb, subsystem, 'signing') + + admin_cert = self.load_admin_cert() + if admin_cert: + self.import_admin_cert(admin_cert) + + self.import_system_cert(nssdb, subsystem, 'sslserver') + self.import_system_cert(nssdb, subsystem, 'subsystem') + self.import_system_cert(nssdb, subsystem, 'audit_signing', ',,P') + + # If provided, import certs and keys from PKCS #12 file + # into NSS database. + + self.import_certs_and_keys(nssdb) + + # If provided, import cert chain into NSS database. + # Note: Cert chain must be imported after the system certs + # to ensure that the system certs are imported with + # the correct nicknames. + + self.import_cert_chain(nssdb, subsystem) + + def update_system_certs(self, subsystem): + + logger.info('Updating system certs') + + if subsystem.name == 'ca': + nickname = self.mdict['pki_ca_signing_nickname'] + subsystem.set_config('ca.signing.cacertnickname', nickname) + + subsystem.set_config( + 'ca.signing.defaultSigningAlgorithm', + self.mdict['pki_ca_signing_signing_algorithm']) + + subsystem.set_config( + 'ca.ocsp_signing.defaultSigningAlgorithm', + self.mdict['pki_ocsp_signing_signing_algorithm']) + + if subsystem.name == 'ocsp': + subsystem.set_config( + 'ocsp.signing.defaultSigningAlgorithm', + self.mdict['pki_ocsp_signing_signing_algorithm']) + + subsystem.set_config( + '%s.audit_signing.defaultSigningAlgorithm' % subsystem.name, + self.mdict['pki_audit_signing_signing_algorithm']) + + def validate_system_certs(self, subsystem): + + logger.info('Validate system certs') + + if subsystem.name == 'ca': + subsystem.validate_system_cert('signing') + subsystem.validate_system_cert('ocsp_signing') + + if subsystem.name == 'kra': + subsystem.validate_system_cert('storage') + subsystem.validate_system_cert('transport') + + if subsystem.name == 'ocsp': + subsystem.validate_system_cert('signing') + + if self.mdict['pki_audit_signing_nickname']: + subsystem.validate_system_cert('audit_signing') + + subsystem.validate_system_cert('sslserver') + subsystem.validate_system_cert('subsystem') + + def record(self, name, record_type, uid, gid, perms, acls=None): + record = manifest.Record() + record.name = name + record.type = record_type + record.user = self.mdict['pki_user'] + record.group = self.mdict['pki_group'] + record.uid = uid + record.gid = gid + record.permissions = perms + record.acls = acls + self.manifest_db.append(record) + + def ds_connect(self): + if not self.ds_url: + logger.debug('ds_connect() called without corresponding call to ds_init()') + self.ds_init() + + ds_url = self.ds_url.geturl() + logger.info('Connecting to LDAP server at %s', ds_url) + + self.ds_connection = ldap.initialize(ds_url) + + def ds_bind(self): + self.ds_connection.simple_bind_s( + self.mdict['pki_ds_bind_dn'], + self.mdict['pki_ds_password']) + + def ds_search(self, key=None): + if key is None: + key = '' + return self.ds_connection.search_s(key, ldap.SCOPE_BASE) + + def ds_close(self): + self.ds_connection.unbind_s() + + def sd_connect(self): + + if self.sd_connection: + return self.sd_connection + + sd_url = self.mdict['pki_security_domain_uri'] + + url = urllib.parse.urlparse(sd_url) + sd_hostname = url.hostname + sd_port = str(url.port) + + logger.info('Connecting to security domain at %s', sd_url) + + conf_dir = os.path.join(pki.server.PKIServer.CONFIG_DIR, + self.mdict['pki_instance_name']) + nssdb_dir = os.path.join(conf_dir, 'alias') + ca_cert = os.path.join(nssdb_dir, 'ca.crt') + + if not os.path.exists(ca_cert): + + # if ca.crt doesn't exist, use provided cert chain + cert_chain_path = self.mdict['pki_cert_chain_path'] + logger.info('Certificate chain: %s', cert_chain_path) + + if cert_chain_path: + + if not os.path.exists(cert_chain_path): + raise Exception('Certificate chain not found: %s' % cert_chain_path) + + ca_cert = cert_chain_path + + self.sd_connection = pki.client.PKIConnection( + protocol='https', + hostname=sd_hostname, + port=sd_port, + trust_env=False, + cert_paths=ca_cert) + + return self.sd_connection + + def get_domain_info(self): + + logger.info('Getting security domain info') + + self.sd_connect() + + sd_client = pki.system.SecurityDomainClient(self.sd_connection) + domain_info = sd_client.get_domain_info() + + return domain_info + + def sd_login(self): + + sd_user = self.mdict['pki_security_domain_user'] + sd_password = self.mdict['pki_security_domain_password'] + + self.sd_connection.authenticate(sd_user, sd_password) + + account = pki.account.AccountClient(self.sd_connection, subsystem='ca') + account.login() + + def sd_logout(self): + account = pki.account.AccountClient(self.sd_connection, subsystem='ca') + account.logout() + + def get_install_token(self): + + logger.info('Getting install token') + + hostname = self.mdict['pki_hostname'] + subsystem = self.subsystem_type + + sd_client = pki.system.SecurityDomainClient(self.sd_connection) + install_token = sd_client.get_install_token(hostname, subsystem) + + # Sleep for a bit to allow the install token to replicate to other clones. + # In the future this can be replaced with signed tokens. + # https://github.com/dogtagpki/pki/issues/2951 + # + # The default sleep time is 5s. + + sd_delay = self.mdict.get('pki_security_domain_post_login_sleep_seconds', '5') + time.sleep(int(sd_delay)) + + return install_token + + def join_security_domain(self): + + sd_url = self.mdict['pki_security_domain_uri'] + + url = urllib.parse.urlparse(sd_url) + sd_hostname = url.hostname + sd_port = str(url.port) + + sd_subsystem = self.domain_info.subsystems['CA'] + self.sd_host = sd_subsystem.get_host(sd_hostname, sd_port) + + self.install_token = self.get_install_token() + + def leave_security_domain(self, subsystem): + + sd_host = subsystem.config.get('securitydomain.host') + + if not sd_host: + return + + sd_port = subsystem.config['securitydomain.httpsadminport'] + sd_url = 'https://%s:%s' % (sd_host, sd_port) + + hostname = subsystem.config['machineName'] + + server_config = self.instance.get_server_config() + secure_port = server_config.get_secure_port() + + proxy_secure_port = subsystem.config.get('proxy.securePort') + if proxy_secure_port: + secure_port = proxy_secure_port + + host_id = '%s %s %s' % (subsystem.type, hostname, secure_port) + + logger.info( + 'Removing %s from security domain at %s', + host_id, + sd_url) + + try: + subsystem.leave_security_domain( + sd_url, + host_id, + hostname, + secure_port) + + except subprocess.CalledProcessError: + logger.error( + 'Unable to remove %s from security domain', subsystem.type) + logger.error('To remove manually:') + logger.error( + '$ pki -U %s -n securitydomain-host-del "%s"', + sd_url, + host_id) + raise + + def setup_security_domain(self, subsystem): + + server_config = self.instance.get_server_config() + unsecurePort = server_config.get_unsecure_port() + securePort = server_config.get_secure_port() + + if self.mdict['pki_security_domain_type'] == 'existing': + + sd_url = self.mdict['pki_security_domain_uri'] + logger.info('Joining security domain at %s', sd_url) + + self.join_security_domain() + + subsystem.set_config('securitydomain.host', self.sd_host.Hostname) + subsystem.set_config('securitydomain.httpport', self.sd_host.Port) + subsystem.set_config('securitydomain.httpsadminport', self.sd_host.SecurePort) + + else: # self.mdict['pki_security_domain_type'] == 'new' + + if config.str2bool(self.mdict['pki_subordinate']) and \ + config.str2bool(self.mdict['pki_subordinate_create_new_security_domain']): + + logger.info('Creating new subordinate security domain') + self.join_security_domain() + + else: + logger.info('Creating new security domain') + + subsystem.set_config('securitydomain.host', self.mdict['pki_hostname']) + subsystem.set_config('securitydomain.httpport', unsecurePort) + subsystem.set_config('securitydomain.httpsadminport', securePort) + + def setup_security_domain_manager(self, subsystem): + + clone = self.configuration_file.clone + + server_config = self.instance.get_server_config() + unsecurePort = server_config.get_unsecure_port() + securePort = server_config.get_secure_port() + + proxyUnsecurePort = subsystem.config.get('proxy.unsecurePort') + if not proxyUnsecurePort: + proxyUnsecurePort = unsecurePort + + proxySecurePort = subsystem.config.get('proxy.securePort') + if not proxySecurePort: + proxySecurePort = securePort + + if self.mdict['pki_security_domain_type'] == 'existing': + + sd_url = self.mdict['pki_security_domain_uri'] + logger.info('Joining security domain at %s', sd_url) + + subsystem.set_config('securitydomain.select', 'existing') + subsystem.set_config('securitydomain.name', self.domain_info.id) + + domain_manager = False + + if subsystem.type == 'CA' and clone: + + # check whether the primary CA is a security domain manager + + sd_hostname = subsystem.config['securitydomain.host'] + sd_port = subsystem.config['securitydomain.httpsadminport'] + + sd_subsystem = self.domain_info.subsystems['CA'] + sd_host = sd_subsystem.get_host(sd_hostname, sd_port) + + if sd_host.DomainManager and sd_host.DomainManager.lower() == 'true': + domain_manager = True + + logger.info('Domain manager: %s', domain_manager) + + if domain_manager: + + logger.info('Cloning security domain manager') + + subsystem.set_config('securitydomain.select', 'new') + subsystem.set_config('securitydomain.host', self.mdict['pki_hostname']) + subsystem.set_config('securitydomain.httpport', unsecurePort) + subsystem.set_config('securitydomain.httpsadminport', securePort) + + subsystem.join_security_domain( + sd_url, + self.mdict['pki_subsystem_name'], + self.mdict['pki_hostname'], + unsecure_port=proxyUnsecurePort, + secure_port=proxySecurePort, + domain_manager=domain_manager, + clone=clone, + session_id=self.install_token.token) + + else: # self.mdict['pki_security_domain_type'] == 'new' + + if config.str2bool(self.mdict['pki_subordinate']) and \ + config.str2bool(self.mdict['pki_subordinate_create_new_security_domain']): + + logger.info('Creating new subordinate security domain') + sd_name = self.mdict['pki_subordinate_security_domain_name'] + + else: + logger.info('Creating new security domain') + sd_name = self.mdict['pki_security_domain_name'] + + subsystem.set_config('securitydomain.select', 'new') + subsystem.set_config('securitydomain.name', sd_name) + + if config.str2bool(self.mdict['pki_ds_setup']): + subsystem.create_security_domain(name=sd_name) + + domain_manager = True + + logger.info('Adding security domain manager') + subsystem.add_security_domain_subsystem( + self.mdict['pki_subsystem_name'], + subsystem.type, + self.mdict['pki_hostname'], + unsecure_port=proxyUnsecurePort, + secure_port=proxySecurePort, + domain_manager=True) + + if domain_manager: + logger.info('Adding security domain sessions') + subsystem.set_config('securitydomain.checkIP', 'false') + subsystem.set_config('securitydomain.checkinterval', '300000') + subsystem.set_config('securitydomain.flushinterval', '86400000') + subsystem.set_config('securitydomain.source', 'ldap') + + def pki_connect(self): + + ca_cert = os.path.join(self.instance.nssdb_dir, "ca.crt") + + connection = pki.client.PKIConnection( + protocol='https', + hostname=self.mdict['pki_hostname'], + port=self.mdict['pki_https_port'], + trust_env=False, + cert_paths=ca_cert) + + self.client = pki.system.SystemConfigClient( + connection, + subsystem=self.mdict['pki_subsystem_type']) + + def import_cert_request(self, subsystem, tag, request): + + request_id_generator = subsystem.config.get('dbs.request.id.generator', 'legacy') + + if request_id_generator == 'legacy': + # call the server to generate legacy request ID + logger.info('Creating request ID for %s cert', tag) + request.systemCert.requestID = self.client.createRequestID(request) + logger.info('- request ID: %s', request.systemCert.requestID) + else: + # let pki-server ca-cert-request-import generate the request ID + request.systemCert.requestID = None + + logger.info('Importing %s cert request into CA database', tag) + request_pem = pki.nssdb.convert_csr(request.systemCert.request, 'base64', 'pem') + result = subsystem.import_cert_request( + request_id=request.systemCert.requestID, + request_data=request_pem, + request_type=request.systemCert.requestType, + profile_path=request.systemCert.profile, + dns_names=request.systemCert.dnsNames, + adjust_validity=request.systemCert.adjustValidity) + + if request_id_generator != 'legacy': + # get the request ID generated by pki-server ca-cert-request-import + request.systemCert.requestID = result['requestID'] + logger.info('- request ID: %s', request.systemCert.requestID) + + def create_system_cert_info(self, subsystem, tag): + + if subsystem.type == 'CA' and tag == 'signing': + cert_id = 'ca_signing' + + elif subsystem.type == 'CA' and tag == 'ocsp_signing': + cert_id = 'ocsp_signing' + + elif subsystem.type == 'KRA' and tag == 'storage': + cert_id = 'storage' + + elif subsystem.type == 'KRA' and tag == 'transport': + cert_id = 'transport' + + elif subsystem.type == 'OCSP' and tag == 'signing': + cert_id = 'ocsp_signing' + + elif tag == 'sslserver': + cert_id = 'sslserver' + + elif tag == 'subsystem': + cert_id = 'subsystem' + + elif tag == 'audit_signing': + cert_id = 'audit_signing' + + else: + raise Exception('Invalid tag for %s: %s' % (subsystem.type, tag)) + + system_cert = pki.system.SystemCertData() + system_cert.keySize = self.mdict['pki_%s_key_size' % cert_id] + system_cert.nickname = self.mdict['pki_%s_nickname' % cert_id] + system_cert.subjectDN = self.mdict['pki_%s_subject_dn' % cert_id] + system_cert.token = self.mdict['pki_%s_token' % cert_id] + system_cert.op_flags = self.mdict['pki_%s_opFlags' % cert_id] + system_cert.op_flags_mask = self.mdict['pki_%s_opFlagsMask' % cert_id] + + if not system_cert.token: + if config.str2bool(self.mdict['pki_hsm_enable']): + system_cert.token = self.mdict['pki_token_name'] + else: + system_cert.token = subsystem.config['preop.module.token'] + + if pki.nssdb.internal_token(system_cert.token): + system_cert.token = pki.nssdb.INTERNAL_TOKEN_NAME + + return system_cert + + def create_cert_setup_request(self, subsystem, tag, cert): + + deploy_tag = tag + if tag == 'signing': # for CA and OCSP + deploy_tag = subsystem.name + '_signing' + + request = pki.system.CertificateSetupRequest() + request.tag = tag + request.pin = self.mdict['pki_one_time_pin'] + + request.systemCert = self.create_system_cert_info(subsystem, tag) + + # cert type: selfsign, local, or remote + request.systemCert.type = self.get_cert_type(subsystem, tag) + + if request.systemCert.type == 'selfsign': + request.systemCert.signingAlgorithm = self.mdict['pki_ca_signing_key_algorithm'] + + elif request.systemCert.type == 'local': + keyalgorithm = self.mdict['pki_ca_signing_key_algorithm'] + signingalgorithm = self.mdict.get('pki_ca_signing_signing_algorithm', keyalgorithm) + request.systemCert.signingAlgorithm = signingalgorithm + + key_type = self.get_key_type(subsystem, tag) + request.systemCert.keyType = key_type + + if key_type == 'RSA': + + if not request.systemCert.keySize: + request.systemCert.keySize = subsystem.config['keys.rsa.keysize.default'] + + if tag == 'transport' or tag == 'storage': + request.systemCert.keyWrap = True + else: + request.systemCert.keyWrap = False + + elif key_type == 'EC': + + request.systemCert.keyCurveName = request.systemCert.keySize + + if not request.systemCert.keyCurveName: + request.systemCert.keyCurveName = subsystem.config['keys.ecc.curve.default'] + + # Default SSL server cert to ECDHE unless stated otherwise. + # Note: IE only supports ECDHE, but ECDH is more efficient. + ec_type = subsystem.config.get('preop.cert.%s.ec.type' % tag, 'ECDHE') + + # For ECDH SSL server cert server.xml should have the following ciphers: + # -TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + # +TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA + # + # For ECDHE SSL server cert server.xml should have the following ciphers: + # +TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + # -TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA + if tag == 'sslserver' and ec_type.upper() == 'ECDH': + request.systemCert.sslECDH = True + else: + request.systemCert.sslECDH = False + + request.systemCert.keyAlgorithm = self.mdict['pki_%s_key_algorithm' % deploy_tag] + + csr_path = subsystem.csr_file(tag) + + # load existing CSR if exists + if os.path.exists(csr_path): + with open(csr_path, 'r', encoding='utf-8') as f: + csr_data = f.read() + request.systemCert.request = pki.nssdb.convert_csr(csr_data, 'pem', 'base64') + + request.systemCert.requestType = 'pkcs10' + + request.systemCert.cert = cert.get('data') + request.systemCert.profile = self.get_cert_profile(subsystem, tag) + request.systemCert.req_ext_oid = subsystem.config.get('preop.cert.%s.ext.oid' % tag) + request.systemCert.req_ext_data = subsystem.config.get('preop.cert.%s.ext.data' % tag) + request.systemCert.req_ext_critical = subsystem.config.get( + 'preop.cert.%s.ext.critical' % tag) + + inject_san = subsystem.config.get('service.injectSAN') + if tag == 'sslserver' and inject_san == 'true': + logger.info('SAN extension:') + dns_names = subsystem.config['service.sslserver.san'].split(',') + for dns_name in dns_names: + logger.info('- %s', dns_name) + request.systemCert.dnsNames = dns_names + else: + request.systemCert.dnsNames = None + + request.systemCert.adjustValidity = tag != 'signing' + + return request + + def find_cert_key(self, tag, request): + + logger.info('Searching for %s key', tag) + + nssdb = self.instance.open_nssdb() + try: + result = nssdb.find_keys( + nickname=request.systemCert.nickname, + token=request.systemCert.token) + finally: + nssdb.close() + + keys = result['entries'] + + if not keys: + return None + + # get the first key + return keys[0]['keyId'] + + def create_cert_key(self, tag, request): + + logger.info('Creating %s key', tag) + + token = request.systemCert.token + key_type = request.systemCert.keyType + op_flags = request.systemCert.op_flags + op_flags_mask = request.systemCert.op_flags_mask + key_size = None + key_wrap = False + curve = None + ssl_ecdh = False + + if request.systemCert.keyType == 'RSA': + key_size = request.systemCert.keySize + key_wrap = request.systemCert.keyWrap + + elif request.systemCert.keyType == 'EC': + curve = request.systemCert.keyCurveName + ssl_ecdh = request.systemCert.sslECDH + + else: + raise Exception('Unsupported key type: %s' % key_type) + + nssdb = self.instance.open_nssdb() + try: + result = nssdb.create_key( + token=token, + key_type=key_type, + key_size=key_size, + key_wrap=key_wrap, + curve=curve, + ssl_ecdh=ssl_ecdh, + op_flags=op_flags, + op_flags_mask=op_flags_mask) + finally: + nssdb.close() + + return result['keyId'] + + def generate_csr(self, + nssdb, + subsystem, + tag, + csr_path, + basic_constraints_ext=None, + key_usage_ext=None, + extended_key_usage_ext=None, + subject_key_id=None, + generic_exts=None): + + cert_id = self.get_cert_id(subsystem, tag) + logger.info('Generating %s CSR in %s', cert_id, csr_path) + csr_pathname = os.path.join(nssdb.tmpdir, os.path.basename(csr_path)) + + subject_dn = self.mdict['pki_%s_subject_dn' % cert_id] + + (key_type, key_size, curve, hash_alg) = self.get_key_params(cert_id) + + """ + For newer HSM in FIPS mode: + for KRA, storage cert and transport cert need to use the new -w + option of PKCS10Client + e.g. PKCS10Client -d /var/lib/pki//alias -h hsm-module + -a rsa -l 2048 -n "CN= KRA storage cert" -w -v -o kra-storage.csr.b64 + + Here we use the pkispawn config param to determine if it's HSM to trigger: + pki_hsm_enable = True + + """ + + logger.debug('generate_csr: pki_hsm_enable: %s', self.mdict['pki_hsm_enable']) + logger.debug('generate_csr: subsystem type: %s', subsystem.type) + + if (subsystem.type == 'KRA' and + config.str2bool(self.mdict['pki_hsm_enable']) and + (cert_id in ['storage', 'transport'])): + + logger.debug('generate_csr: calling PKCS10Client for %s', cert_id) + + nssdb.create_request_with_wrapping_key( + subject_dn=subject_dn, + request_file=csr_path, + key_size=key_size) + + else: + + logger.debug('generate_csr: calling certutil for %s', cert_id) + + nssdb.create_request( + subject_dn=subject_dn, + request_file=csr_pathname, + key_type=key_type, + key_size=key_size, + curve=curve, + hash_alg=hash_alg, + basic_constraints_ext=basic_constraints_ext, + key_usage_ext=key_usage_ext, + extended_key_usage_ext=extended_key_usage_ext, + subject_key_id=subject_key_id, + generic_exts=generic_exts, + use_jss=True) + + shutil.move(csr_pathname, csr_path) + + new_csr_path = subsystem.csr_file(tag) + self.file.copy( + old_name=csr_path, + new_name=new_csr_path, + overwrite_flag=True) + self.instance.chown(new_csr_path) + + def create_cert_request(self, nssdb, tag, request): + + if request.systemCert.requestType != 'pkcs10': + raise Exception( + 'Certificate request type not supported: %s' % request.systemCert.requestType) + + # match with + match = re.fullmatch(r'(\S+)with(\S+)', request.systemCert.keyAlgorithm) + hash_alg = None + if match: + hash_alg = match.group(1) + + basic_constraints_ext = None + key_usage_ext = None + generic_exts = None + + if tag == 'signing': + + basic_constraints_ext = { + 'ca': True, + 'path_length': None, + 'critical': True + } + + key_usage_ext = { + 'digitalSignature': True, + 'nonRepudiation': True, + 'certSigning': True, + 'crlSigning': True, + 'critical': True + } + + # NSCertTypeExtension (unsupported) + # ns_cert_type_ext = { + # 'nsCertType': True, + # 'ssl_ca': True + # } + + if request.systemCert.req_ext_oid and request.systemCert.req_ext_data: + + generic_ext = { + 'oid': request.systemCert.req_ext_oid, + 'data': binascii.unhexlify(request.systemCert.req_ext_data), + 'critical': config.str2bool(request.systemCert.req_ext_critical) + } + + generic_exts = [generic_ext] + + tmpdir = tempfile.mkdtemp() + try: + csr_file = os.path.join(tmpdir, 'request.csr') + + nssdb.create_request( + subject_dn=request.systemCert.subjectDN, + request_file=csr_file, + token=request.systemCert.token, + key_id=request.systemCert.keyID, + hash_alg=hash_alg, + basic_constraints_ext=basic_constraints_ext, + key_usage_ext=key_usage_ext, + generic_exts=generic_exts, + use_jss=True) + + with open(csr_file, encoding='utf-8') as f: + pem_csr = f.read() + + return pki.nssdb.convert_csr(pem_csr, 'pem', 'base64') + + finally: + shutil.rmtree(tmpdir) + + def create_temp_sslserver_cert(self): + + hostname = self.mdict['pki_hostname'] + + (key_type, key_size, curve, hash_alg) = self.get_key_params('sslserver') + + nickname = self.mdict['pki_self_signed_nickname'] + token = self.mdict['pki_self_signed_token'] + + subject_dn = \ + 'cn=' + self.mdict['pki_hostname'] + ',' + \ + 'o=' + self.mdict['pki_certificate_timestamp'] + + serial = self.mdict.get('pki_self_signed_serial_number') + validity = self.mdict.get('pki_self_signed_validity_period') + trust_attributes = self.mdict.get('pki_self_signed_trustargs') + + self.instance.set_sslserver_cert_nickname(nickname) + + tmpdir = tempfile.mkdtemp() + nssdb = self.instance.open_nssdb() + + try: + logger.info('Checking existing temp SSL server cert: %s', nickname) + pem_cert = nssdb.get_cert(nickname=nickname) + + if pem_cert: + cert = x509.load_pem_x509_certificate(pem_cert, default_backend()) + cn = cert.subject.get_attributes_for_oid(x509.oid.NameOID.COMMON_NAME)[0] + cert_hostname = cn.value + + logger.info('Existing temp SSL server cert is for %s', cert_hostname) + + # if cert hostname is correct, don't create new temp cert + if cert_hostname == hostname: + return + + logger.info('Removing existing temp SSL server cert for %s', cert_hostname) + + nssdb.remove_cert(nickname=nickname, remove_key=True) + + logger.info('Creating new temp SSL server cert for %s', hostname) + + # TODO: replace with pki-server create-cert --temp sslserver + + # NOTE: ALWAYS create the temporary sslserver certificate + # in the software DB regardless of whether the + # instance will utilize 'softokn' or an HSM + + csr_file = os.path.join(tmpdir, 'sslserver.csr') + cert_file = os.path.join(tmpdir, 'sslserver.crt') + + nssdb.create_request( + subject_dn=subject_dn, + request_file=csr_file, + token=token, + key_type=key_type, + key_size=key_size, + curve=curve, + hash_alg=hash_alg, + use_jss=True + ) + + nssdb.create_cert( + request_file=csr_file, + cert_file=cert_file, + serial=serial, + validity=validity, + use_jss=True + ) + + nssdb.add_cert( + nickname=nickname, + cert_file=cert_file, + token=token, + trust_attributes=trust_attributes) + + finally: + nssdb.close() + shutil.rmtree(tmpdir) + + def remove_temp_sslserver_cert(self): + + nickname = self.mdict['pki_self_signed_nickname'] + logger.info('Removing temp SSL server cert: %s', nickname) + + nssdb = self.instance.open_nssdb() + + try: + # remove temp SSL server cert and key + nssdb.remove_cert(nickname=nickname, remove_key=True) + + finally: + nssdb.close() + + def update_sslserver_cert_nickname(self, subsystem): + + sslserver = subsystem.get_subsystem_cert('sslserver') + nickname = sslserver['nickname'] + token = sslserver['token'] + self.instance.set_sslserver_cert_nickname(nickname, token) + + def create_cert_id(self, subsystem, tag, request): + + cert_id_generator = subsystem.config.get('dbs.cert.id.generator', 'legacy') + + if cert_id_generator == 'legacy': + # call the server to generate legacy cert ID + logger.info('Creating cert ID for %s cert', tag) + cert_id = self.client.createCertID(request) + logger.info('- cert ID: %s', cert_id) + + else: + # let pki-server ca-cert-create generate the cert ID + cert_id = None + + return cert_id + + def create_cert(self, subsystem, request): + + cert_data = subsystem.create_cert( + request_id=request.systemCert.requestID, + profile_path=request.systemCert.profile, + cert_type=request.systemCert.type, + key_id=request.systemCert.keyID, + key_token=request.systemCert.token, + key_algorithm=request.systemCert.keyAlgorithm, + signing_algorithm=request.systemCert.signingAlgorithm, + serial=request.systemCert.certID, + cert_format='DER') + + return base64.b64encode(cert_data).decode('ascii') + + def import_cert(self, subsystem, tag, request, cert_data): + + logger.info('Importing %s cert into CA database', tag) + logger.debug('- cert: %s', cert_data) + + pem_cert = pki.nssdb.convert_cert(cert_data, 'base64', 'pem') + + subsystem.import_cert( + cert_data=pem_cert.encode('utf-8'), + cert_format='PEM', + profile_path=request.systemCert.profile, + request_id=request.systemCert.requestID) + + def setup_system_cert(self, nssdb, subsystem, tag, system_cert, request): + + logger.debug('PKIDeployer.setup_system_cert()') + + if not request.systemCert.nickname: + # skip cert setup + return + + # Check whether the cert already exists in NSS database + + cert_info = nssdb.get_cert_info( + nickname=request.systemCert.nickname, + token=request.systemCert.token) + + if cert_info: + logger.info('%s cert already exists in NSS database', tag) + else: + logger.info('%s cert does not exist in NSS database', tag) + + # For external/existing CA case, the requests and certs might be provided + # (i.e. already exists in NSS database), but they still need to be imported + # into CA database. + # + # A new SSL server cert will always be created separately later. + + external = config.str2bool(self.mdict['pki_external']) + + if subsystem.type == 'CA' and external and cert_info: + + logger.info('Reusing %s cert in NSS database', tag) + logger.info('- nickname: %s', request.systemCert.nickname) + logger.info('- serial: %s', hex(cert_info['serial_number'])) + logger.info('- subject: %s', cert_info['subject']) + logger.info('- issuer: %s', cert_info['issuer']) + logger.info('- trust flags: %s', cert_info['trust_flags']) + + signing_cert_info = nssdb.get_cert_info( + nickname=subsystem.config["ca.signing.nickname"]) + logger.info('CA subject: %s', signing_cert_info['subject']) + + if cert_info['object'].issuer != signing_cert_info['object'].subject: + logger.info('Do not import external cert and request into database: %s', tag) + return + + # When importing existing self-signed CA certificate, create a + # certificate record to reserve the serial number. Otherwise it + # might conflict with system certificates to be created later. + # Also create the certificate request record for renewals. + + if config.str2bool(self.mdict['pki_import_system_certs']) and \ + config.str2bool(self.mdict['pki_ds_setup']): + self.import_cert_request(subsystem, tag, request) + self.import_cert(subsystem, tag, request, system_cert['data']) + + return + + csr_file = subsystem.csr_file(tag) + if os.path.exists(csr_file): + logger.info('Reusing %s cert request in %s', tag, csr_file) + + else: + if cert_info: + request.systemCert.keyID = self.find_cert_key(tag, request) + + if request.systemCert.keyID: + logger.info('Reusing %s key in NSS database', tag) + else: + logger.info('Creating new %s key in NSS database', tag) + request.systemCert.keyID = self.create_cert_key(tag, request) + + logger.info('- key ID: %s', request.systemCert.keyID) + + logger.info('Creating %s cert request', tag) + request.systemCert.request = self.create_cert_request(nssdb, tag, request) + logger.debug('- request: %s', request.systemCert.request) + + system_cert['request'] = request.systemCert.request + + if tag != 'sslserver' and tag != 'subsystem': + cert_id = subsystem.name + '_' + tag + else: + cert_id = tag + + logger.info('Storing %s cert request', tag) + self.instance.store_cert_request(cert_id, system_cert) + + if request.systemCert.type == 'remote': + + if cert_info: + logger.info('Reusing %s cert in NSS database', tag) + logger.info('- nickname: %s', request.systemCert.nickname) + logger.info('- serial: %s', hex(cert_info['serial_number'])) + logger.info('- subject: %s', cert_info['subject']) + logger.info('- issuer: %s', cert_info['issuer']) + logger.info('- trust flags: %s', cert_info['trust_flags']) + return + + # Issue subordinate CA signing cert using remote CA signing cert. + + if subsystem.type == 'CA' and \ + config.str2bool(self.mdict['pki_clone']) \ + and tag == 'sslserver': + + # For CA clone always use the master CA to generate the SSL + # server certificate to avoid any changes which may have + # been made to the X500Name directory string encoding order. + ca_url = self.mdict['pki_clone_uri'] + + elif tag == 'subsystem': + + sd_hostname = subsystem.config['securitydomain.host'] + sd_port = subsystem.config['securitydomain.httpsadminport'] + ca_url = 'https://%s:%s' % (sd_hostname, sd_port) + + else: + + ca_url = self.mdict['pki_issuing_ca'] + + hostname = self.mdict['pki_hostname'] + + server_config = self.instance.get_server_config() + secure_port = server_config.get_secure_port() + + requestor = '%s-%s-%s' % (subsystem.type, hostname, secure_port) + + logger.info('Requesting %s cert from %s', tag, ca_url) + + cert_pem = self.request_cert( + ca_url, + request.systemCert.requestType, + request.systemCert.request, + request.systemCert.profile, + request.systemCert.subjectDN, + dns_names=request.systemCert.dnsNames, + requestor=requestor) + + system_cert['data'] = pki.nssdb.convert_cert(cert_pem, 'pem', 'base64') + + cert_obj = x509.load_pem_x509_certificate( + bytes(cert_pem, 'utf-8'), + backend=default_backend()) + + logger.info('- serial: %s', hex(cert_obj.serial_number)) + logger.info('- subject: %s', cert_obj.subject.rfc4514_string()) + logger.info('- issuer: %s', cert_obj.issuer.rfc4514_string()) + + logger.info('Importing %s cert into NSS database', tag) + logger.info('- nickname: %s', request.systemCert.nickname) + + nssdb.add_cert( + nickname=request.systemCert.nickname, + cert_data=system_cert['data'], + cert_format='base64', + token=request.systemCert.token) + + return + + # selfsign or local + + if config.str2bool(self.mdict['pki_ds_setup']): + # import request into CA database and get a request ID + self.import_cert_request(subsystem, tag, request) + + if cert_info: + logger.info('Reusing %s cert in NSS database', tag) + logger.info('- nickname: %s', request.systemCert.nickname) + logger.info('- serial: %s', hex(cert_info['serial_number'])) + logger.info('- subject: %s', cert_info['subject']) + logger.info('- issuer: %s', cert_info['issuer']) + logger.info('- trust flags: %s', cert_info['trust_flags']) + + else: + request.systemCert.certID = self.create_cert_id(subsystem, tag, request) + + logger.info('Creating %s cert', tag) + system_cert['data'] = self.create_cert(subsystem, request) + + cert_pem = pki.nssdb.convert_cert(system_cert['data'], 'base64', 'pem').encode() + cert_obj = x509.load_pem_x509_certificate(cert_pem, backend=default_backend()) + logger.info('- serial: %s', hex(cert_obj.serial_number)) + logger.info('- subject: %s', cert_obj.subject.rfc4514_string()) + logger.info('- issuer: %s', cert_obj.issuer.rfc4514_string()) + + logger.info('Importing %s cert into NSS database', tag) + logger.info('- nickname: %s', request.systemCert.nickname) + + nssdb.add_cert( + nickname=request.systemCert.nickname, + cert_data=system_cert['data'], + cert_format='base64', + token=request.systemCert.token) + + if config.str2bool(self.mdict['pki_ds_setup']): + # import cert into CA database + self.import_cert(subsystem, tag, request, system_cert['data']) + + def setup_system_certs(self, nssdb, subsystem): + + logger.debug('PKIDeployer.setup_system_certs()') + system_certs = {} + + clone = self.configuration_file.clone + num_subsystems = len(self.instance.get_subsystems()) + + external = config.str2bool(self.mdict['pki_external']) or \ + config.str2bool(self.mdict['pki_standalone']) + + tags = subsystem.config['%s.cert.list' % subsystem.name].split(',') + + for tag in tags: + + logger.info('Setting up %s cert', tag) + + system_cert = subsystem.get_subsystem_cert(tag) + system_certs[tag] = system_cert + + if tag != 'sslserver' and clone: + logger.info('%s cert is already set up', tag) + continue + + if tag == 'sslserver' and num_subsystems > 1: + logger.info('sslserver cert is already set up') + continue + + if tag == 'subsystem' and num_subsystems > 1: + logger.info('subsystem cert is already set up') + continue + + # For external/standalone KRA/OCSP/TKS/TPS case, all system certs will be provided. + # No system certs will be generated including the SSL server cert. + + if subsystem.type in ['KRA', 'OCSP', 'TKS', 'TPS'] and external: + continue + + request = self.create_cert_setup_request(subsystem, tag, system_cert) + + self.setup_system_cert(nssdb, subsystem, tag, system_cert, request) + + logger.info('Setting up trust flags') + + if pki.nssdb.internal_token(self.mdict.get('pki_token_name')): + token = '' + else: + token = self.mdict['pki_token_name'] + ':' + + if subsystem.type == 'CA': + nssdb.modify_cert( + nickname=token + self.mdict['pki_ca_signing_nickname'], + trust_attributes='CTu,Cu,Cu') + + if self.mdict['pki_audit_signing_nickname']: + nssdb.modify_cert( + nickname=token + self.mdict['pki_audit_signing_nickname'], + trust_attributes='u,u,Pu') + + # update NSS database owner + self.instance.chown(self.instance.nssdb_dir) + + # update NSS database file permissions + for filename in os.listdir(self.instance.nssdb_dir): + pki.util.chmod( + os.path.join(self.instance.nssdb_dir, filename), + config.PKI_DEPLOYMENT_DEFAULT_SECURITY_DATABASE_PERMISSIONS) + + # update NSS database folder permission + os.chmod( + self.instance.nssdb_dir, + pki.server.DEFAULT_DIR_MODE) + + return system_certs + + def request_cert( + self, + url, + request_type, + csr, + profile, + subject, + dns_names=None, + requestor=None): + + tmpdir = tempfile.mkdtemp() + try: + pem_csr = pki.nssdb.convert_csr(csr, 'base64', 'pem') + csr_file = os.path.join(tmpdir, 'request.csr') + with open(csr_file, 'w', encoding='utf-8') as f: + f.write(pem_csr) + + install_token = os.path.join(tmpdir, 'install-token') + with open(install_token, 'w', encoding='utf-8') as f: + f.write(self.install_token.token) + + cmd = [ + 'pki', + '-d', self.instance.nssdb_dir, + '-f', self.instance.password_conf, + '-U', url, + '--ignore-banner', + 'ca-cert-request-submit', + '--request-type', request_type, + '--csr-file', csr_file, + '--profile', profile, + '--subject', subject + ] + + if dns_names: + cmd.extend(['--dns-names', ','.join(dns_names)]) + + if requestor: + cmd.extend(['--requestor', requestor]) + + cmd.extend([ + '--install-token', install_token, + '--output-format', 'PEM' + ]) + + if logger.isEnabledFor(logging.DEBUG): + cmd.append('--debug') + + elif logger.isEnabledFor(logging.INFO): + cmd.append('--verbose') + + logger.debug('Command: %s', ' '.join(cmd)) + result = subprocess.run(cmd, stdout=subprocess.PIPE, check=True) + + return result.stdout.decode() + + finally: + shutil.rmtree(tmpdir) + + def create_admin_csr(self, subsystem): + + if self.mdict['pki_admin_cert_request_type'] != 'pkcs10': + raise Exception(log.PKI_CONFIG_PKCS10_SUPPORT_ONLY) + + csr_path = os.path.join(self.mdict['pki_client_database_dir'], 'admin.csr') + + client_nssdb = pki.nssdb.NSSDatabase( + directory=self.mdict['pki_client_database_dir'], + password_file=self.mdict['pki_client_password_conf']) + + try: + self.generate_csr( + client_nssdb, + subsystem, + 'admin', + csr_path + ) + + finally: + client_nssdb.close() + + with open(csr_path, encoding='utf-8') as f: + pem_csr = f.read() + + return pki.nssdb.convert_csr(pem_csr, 'pem', 'base64') + + def valid_algorithm(self, key_type, algorithm): + + if key_type == 'RSA' and 'RSA' in algorithm: + return True + + if key_type == 'EC' and 'EC' in algorithm: + return True + + if key_type == 'DSA' and 'DSA' in algorithm: + return True + + return False + + def get_signing_algorithm(self, subsystem, profile): + ''' + Get the signing algorithm from a profile. + + First, get the allowed algorithms from the profile (constraint.params.signingAlgsAllowed). + If the property does not exist, get the ca.profiles.defaultSigningAlgsAllowed from CS.cfg. + If the property does not exist, use the default: SHA256withRSA, SHA256withEC. + + Next, get the default signing algorithm from the profile (default.params.signingAlg). + If the property exists and matches the signing CA key type, return the algorithm. + If the property does not exist or equals '-', get the first allowed algorithm + that matches the CA signing key type. + ''' + + key_type = self.get_key_type(subsystem, 'signing') + logger.info('Key type: %s', key_type) + + algorithm = None + allowed_algorithms = None + + # get default algorithm and allowed algorithms from profile + for name in profile: + value = profile[name] + + if name.endswith('default.params.signingAlg'): + algorithm = value.strip() + + if name.endswith('constraint.params.signingAlgsAllowed'): + allowed_algorithms = value.split(',') + + # if profile does not define allowed algorithms, use the one from CS.cfg + if not allowed_algorithms: + default_allowed_algorithms = subsystem.config.get( + 'ca.profiles.defaultSigningAlgsAllowed', + 'SHA256withRSA,SHA256withEC') + allowed_algorithms = default_allowed_algorithms.split(',') + + logger.info('Allowed signing algorithms: %s', ','.join(allowed_algorithms)) + + if not allowed_algorithms: + raise Exception('Unable to get allowed signing algorithms') + + # check algorithm + if algorithm and algorithm != '-': + + if not self.valid_algorithm(key_type, algorithm): + raise Exception('Invalid signing algorithm: %s' % algorithm) + + if algorithm not in allowed_algorithms: + raise Exception('Signing algorithm not allowed: %s' % algorithm) + + return algorithm + + # get the first allowed algorithm + for algorithm in allowed_algorithms: + + if not self.valid_algorithm(key_type, algorithm): + continue + + return algorithm + + raise Exception('Unable to get signing algorithm') + + def create_admin_cert(self, subsystem, csr): + + request = pki.system.CertificateSetupRequest() + request.tag = 'admin' + request.pin = self.mdict['pki_one_time_pin'] + + request.systemCert = pki.system.SystemCertData() + + request.systemCert.type = self.get_cert_type(subsystem, 'admin') + + if request.systemCert.type == 'local': + keyalgorithm = self.mdict['pki_ca_signing_key_algorithm'] + signingalgorithm = self.mdict.get('pki_ca_signing_signing_algorithm', keyalgorithm) + request.systemCert.signingAlgorithm = signingalgorithm + + request.systemCert.keyType = self.mdict['pki_admin_key_type'] + request.systemCert.profile = self.get_cert_profile(subsystem, 'admin') + request.systemCert.subjectDN = self.mdict['pki_admin_subject_dn'] + + request.systemCert.requestType = self.mdict['pki_admin_cert_request_type'] + request.systemCert.request = csr + + request.systemCert.dnsNames = None + request.systemCert.adjustValidity = False + + profile_filename = os.path.join( + subsystem.conf_dir, + 'profiles/ca/%s.cfg' % self.mdict['pki_admin_profile_id']) + logger.info('Loading %s', profile_filename) + + profile = {} + pki.util.load_properties(profile_filename, profile) + + request.systemCert.keyAlgorithm = self.get_signing_algorithm(subsystem, profile) + logger.info('Signing algorithm: %s', request.systemCert.keyAlgorithm) + + if config.str2bool(self.mdict['pki_ds_setup']): + self.import_cert_request(subsystem, 'admin', request) + + request.systemCert.certID = self.create_cert_id(subsystem, 'admin', request) + + logger.info('Creating admin cert') + cert_data = self.create_cert(subsystem, request) + + cert_pem = pki.nssdb.convert_cert(cert_data, 'base64', 'pem') + cert_obj = x509.load_pem_x509_certificate(cert_pem.encode(), backend=default_backend()) + logger.info('- serial: %s', hex(cert_obj.serial_number)) + + if config.str2bool(self.mdict['pki_ds_setup']): + self.import_cert(subsystem, 'admin', request, cert_data) + + return cert_pem + + def setup_admin_cert(self, subsystem): + + logger.debug('PKIDeployer.setup_admin_cert()') + + external = config.str2bool(self.mdict['pki_external']) + standalone = config.str2bool(self.mdict['pki_standalone']) + + nickname = self.mdict['pki_admin_nickname'] + + client_nssdb = pki.nssdb.NSSDatabase( + directory=self.mdict['pki_client_database_dir'], + password_file=self.mdict['pki_client_password_conf']) + + try: + logger.info('Checking %s cert in %s', nickname, client_nssdb.directory) + cert_info = client_nssdb.get_cert_info(nickname) + + # If the admin cert doesn't exist in the client NSS database and the admin + # PKCS #12 file is specified and not empty, import the PKCS #12 file. + # This check is necessary since IPA specifies an empty admin PKCS #12 file: + # https://github.com/freeipa/freeipa/blob/master/ipaserver/install/krainstance.py + + pkcs12_file = self.mdict['pki_client_admin_cert_p12'] + if not cert_info and pkcs12_file \ + and os.path.exists(pkcs12_file) \ + and os.path.getsize(pkcs12_file) > 0: + + logger.info('Importing admin cert from %s', pkcs12_file) + pkcs12_password = self.mdict['pki_client_pkcs12_password'] + + client_nssdb.import_pkcs12( + pkcs12_file=pkcs12_file, + pkcs12_password=pkcs12_password) + + cert_info = client_nssdb.get_cert_info(nickname) + + if cert_info: + logger.info('Found %s cert:', nickname) + logger.info('- serial: %s', hex(cert_info['serial_number'])) + logger.info('- subject: %s', cert_info['subject']) + logger.info('- issuer: %s', cert_info['issuer']) + logger.info('- trust flags: %s', cert_info['trust_flags']) + + pem_cert = pki.nssdb.convert_cert(cert_info['data'], 'base64', 'pem') + + else: + logger.info('admin cert does not exist in NSS database') + pem_cert = None + + finally: + client_nssdb.close() + + if pem_cert: + logger.debug('Admin cert:\n%s', pem_cert) + + if external and subsystem.type != 'CA' or standalone: + self.import_admin_cert(pem_cert) + self.store_admin_cert(pem_cert) + self.export_admin_pkcs12() + + return pem_cert + + cert_path = self.mdict.get('pki_admin_cert_path') + if cert_path: + + logger.info('Loading admin cert from %s', cert_path) + with open(cert_path, 'r', encoding='utf-8') as f: + pem_cert = f.read() + + logger.debug('Admin cert:\n%s', pem_cert) + + if external and subsystem.type != 'CA' or standalone: + self.import_admin_cert(pem_cert) + self.store_admin_cert(pem_cert) + self.export_admin_pkcs12() + + return pem_cert + + cert_file = self.mdict.get('pki_admin_cert_file') + logger.info('Checking admin cert in %s', cert_file) + + if cert_file and os.path.exists(cert_file): + + logger.info('Loading admin cert from %s', cert_file) + with open(cert_file, 'r', encoding='utf-8') as f: + pem_cert = f.read() + + logger.debug('Admin cert:\n%s', pem_cert) + + if external and subsystem.type != 'CA' or standalone: + self.import_admin_cert(pem_cert) + self.store_admin_cert(pem_cert) + self.export_admin_pkcs12() + + return pem_cert + + logger.info('Creating admin cert request') + admin_csr = self.create_admin_csr(subsystem) + + if subsystem.type == 'CA': + logger.info('Creating admin cert') + pem_cert = self.create_admin_cert(subsystem, admin_csr) + logger.debug('Admin cert:\n%s', pem_cert) + + self.import_admin_cert(pem_cert) + self.store_admin_cert(pem_cert) + self.export_admin_pkcs12() + + return pem_cert + + ca_type = subsystem.config['preop.ca.type'] + + if ca_type == 'sdca': + ca_url = self.mdict['pki_issuing_ca'] + + else: + ca_hostname = subsystem.config['securitydomain.host'] + ca_port = subsystem.config['securitydomain.httpsadminport'] + ca_url = 'https://%s:%s' % (ca_hostname, ca_port) + + logger.info('Requesting admin cert from %s', ca_url) + + request_type = self.mdict['pki_admin_cert_request_type'] + + key_type = self.mdict['pki_admin_key_type'] + + if key_type.lower() == 'ecc': + profile = 'caECAdminCert' + else: + profile = self.mdict['pki_admin_profile_id'] + + subject = self.mdict['pki_admin_subject_dn'] + + pem_cert = self.request_cert( + ca_url, + request_type, + admin_csr, + profile, + subject) + + logger.debug('Admin cert:\n%s', pem_cert) + + self.import_admin_cert(pem_cert) + self.store_admin_cert(pem_cert) + self.export_admin_pkcs12() + + return pem_cert + + def setup_admin_user(self, subsystem, cert_data): + + uid = self.mdict['pki_admin_uid'] + full_name = self.mdict['pki_admin_name'] + email = self.mdict['pki_admin_email'] + password = self.mdict['pki_admin_password'] + + tps_profiles = None + if subsystem.type == 'TPS': + tps_profiles = ['All Profiles'] + + # Run the command as current user such that + # it can read the temporary password file. + + subsystem.add_user( + uid, + full_name=full_name, + email=email, + password=password, + user_type='adminType', + state='1', + tps_profiles=tps_profiles, + ignore_duplicate=True, + as_current_user=True) + + groups = ['Administrators'] + + if subsystem.type == 'CA': + groups.append('Certificate Manager Agents') + + elif subsystem.type == 'KRA': + groups.append('Data Recovery Manager Agents') + + elif subsystem.type == 'OCSP': + groups.append('Online Certificate Status Manager Agents') + + elif subsystem.type == 'TKS': + groups.append('Token Key Service Manager Agents') + + elif subsystem.type == 'TPS': + groups.append('TPS Agents') + groups.append('TPS Operators') + + if subsystem.config.get('securitydomain.select') == 'new': + + if subsystem.type == 'CA': + groups.extend([ + 'Security Domain Administrators', + 'Enterprise CA Administrators', + 'Enterprise KRA Administrators', + 'Enterprise RA Administrators', + 'Enterprise TKS Administrators', + 'Enterprise OCSP Administrators', + 'Enterprise TPS Administrators' + ]) + + elif subsystem.type == 'KRA': + groups.extend([ + 'Security Domain Administrators', + 'Enterprise KRA Administrators' + ]) + + elif subsystem.type == 'OCSP': + groups.extend([ + 'Security Domain Administrators', + 'Enterprise OCSP Administrators' + ]) + + for group in groups: + logger.info('Adding %s into %s', uid, group) + subsystem.add_group_member(group, uid) + + logger.info('Adding certificate for %s', uid) + subsystem.add_user_cert( + uid, + cert_data=cert_data.encode(), + cert_format='PEM', + ignore_duplicate=True) + + def setup_subsystem_user(self, subsystem, cert): + + server_config = self.instance.get_server_config() + secure_port = server_config.get_secure_port() + + uid = 'CA-%s-%s' % (self.mdict['pki_hostname'], secure_port) + + subsystem.add_user( + uid, + full_name=uid, + user_type='agentType', + state='1', + ignore_duplicate=True) + + cert_data = pki.nssdb.convert_cert( + cert['data'], + 'base64', + 'pem') + + logger.info('Adding certificate for %s', uid) + + try: + subsystem.add_user_cert( + uid, + cert_data=cert_data.encode(), + cert_format='PEM', + ignore_duplicate=True) + + except Exception: # pylint: disable=W0703 + logger.warning('Unable to add certificate for %s', uid) + # TODO: ignore error only if user cert already exists + + logger.info('Adding %s into Subsystem Group', uid) + + try: + subsystem.add_group_member('Subsystem Group', uid) + except Exception: # pylint: disable=W0703 + logger.warning('Unable to add %s into Subsystem Group', uid) + # TODO: ignore failure only if user already exists in the group + + def backup_keys(self, subsystem): + + tmpdir = tempfile.mkdtemp() + try: + password_file = os.path.join(tmpdir, 'password.txt') + with open(password_file, 'w', encoding='utf-8') as f: + f.write(self.mdict['pki_backup_password']) + + cmd = [ + 'pki-server', + 'subsystem-cert-export', + subsystem.name, + '-i', self.instance.name, + '--pkcs12-file', self.mdict['pki_backup_file'], + '--pkcs12-password-file', password_file + ] + + logger.debug('Command: %s', ' '.join(cmd)) + subprocess.run(cmd, check=True) + + finally: + shutil.rmtree(tmpdir) + + def setup_database_user(self, subsystem): + + subsystem.add_user( + 'pkidbuser', + full_name='pkidbuser', + user_type='agentType', + state='1', + attributes={ + 'nsPagedSizeLimit': '20000' + }, + ignore_duplicate=True) + + subsystem_cert = subsystem.get_subsystem_cert('subsystem') + subject = subsystem_cert['subject'] + + nssdb = self.instance.open_nssdb() + try: + cert_data = nssdb.get_cert( + nickname=subsystem_cert['nickname'], + token=subsystem_cert['token']) + finally: + nssdb.close() + + logger.info('Adding subsystem cert into pkidbuser') + subsystem.add_user_cert( + 'pkidbuser', + cert_data=cert_data, + cert_format='PEM', + ignore_duplicate=True) + + logger.info('Linking pkidbuser to subsystem cert: %s', subject) + subsystem.modify_user('pkidbuser', add_see_also=subject) + + logger.info('Finding other users linked to subsystem cert') + users = subsystem.find_users(see_also=subject) + + for user in users['entries']: + uid = user['id'] + + if uid == 'pkidbuser': + continue + + logger.info('Unlinking %s from subsystem cert ', uid) + subsystem.modify_user(uid, del_see_also=subject) + + # workaround for https://github.com/dogtagpki/pki/issues/2154 + + if subsystem.type == 'CA': + groups = ['Subsystem Group', 'Certificate Manager Agents'] + + elif subsystem.type == 'KRA': + groups = ['Data Recovery Manager Agents', 'Trusted Managers'] + + elif subsystem.type == 'OCSP': + groups = ['Trusted Managers'] + + elif subsystem.type == 'TKS': + groups = ['Token Key Service Manager Agents'] + + else: + groups = [] + + for group in groups: + logger.info('Adding pkidbuser into %s', group) + subsystem.add_group_member(group, 'pkidbuser') + + def add_subsystem_user( + self, + subsystem_type, + subsystem_url, + uid, + full_name, + cert=None, + session=None, + install_token=None): + + sd_url = self.mdict['pki_security_domain_uri'] + + tmpdir = tempfile.mkdtemp() + try: + if not install_token: + install_token = os.path.join(tmpdir, 'install-token') + with open(install_token, 'w', encoding='utf-8') as f: + f.write(session) + + cmd = [ + 'pki', + '-d', self.instance.nssdb_dir, + '-f', self.instance.password_conf, + '-U', subsystem_url, + '--ignore-banner', + '%s-user-add' % subsystem_type, + uid, + '--security-domain', sd_url, + '--install-token', install_token, + '--fullName', full_name + ] + + if cert: + cert_file = os.path.join(tmpdir, 'cert.pem') + with open(cert_file, 'w', encoding='utf-8') as f: + f.write(cert) + cmd.extend(['--cert-file', cert_file]) + + if logger.isEnabledFor(logging.DEBUG): + cmd.append('--debug') + + elif logger.isEnabledFor(logging.INFO): + cmd.append('--verbose') + + logger.debug('Command: %s', ' '.join(cmd)) + subprocess.check_call(cmd) + + finally: + shutil.rmtree(tmpdir) + + def get_ca_signing_cert(self, ca_url): + + cmd = [ + 'pki', + '-d', self.instance.nssdb_dir, + '-f', self.instance.password_conf, + '-U', ca_url, + '--ignore-cert-status', 'UNTRUSTED_ISSUER', + '--ignore-banner', + 'ca-cert-signing-export', + '--pkcs7' + ] + + if logger.isEnabledFor(logging.DEBUG): + cmd.append('--debug') + + elif logger.isEnabledFor(logging.INFO): + cmd.append('--verbose') + + logger.debug('Command: %s', ' '.join(cmd)) + output = subprocess.check_output(cmd) + + return output.decode() + + def get_ca_subsystem_cert(self, ca_url): + + cmd = [ + 'pki', + '-d', self.instance.nssdb_dir, + '-f', self.instance.password_conf, + '-U', ca_url, + '--ignore-banner', + 'ca-cert-subsystem-export' + ] + + if logger.isEnabledFor(logging.DEBUG): + cmd.append('--debug') + + elif logger.isEnabledFor(logging.INFO): + cmd.append('--verbose') + + logger.debug('Command: %s', ' '.join(cmd)) + return subprocess.check_output(cmd) + + def add_kra_connector(self, subsystem, ca_url): + + server_config = self.instance.get_server_config() + hostname = self.mdict['pki_hostname'] + securePort = server_config.get_secure_port() + + kra_url = 'https://%s:%s/kra/agent/kra/connector' % (hostname, securePort) + + subsystem_cert = subsystem.get_subsystem_cert('subsystem').get('data') + transport_cert_info = subsystem.get_subsystem_cert('transport') + transport_cert = transport_cert_info.get('data') + transport_nickname = transport_cert_info.get('nickname') + + tmpdir = tempfile.mkdtemp() + try: + subsystem_cert_file = os.path.join(tmpdir, 'subsystem.crt') + with open(subsystem_cert_file, 'w', encoding='utf-8') as f: + f.write(subsystem_cert) + + transport_cert_file = os.path.join(tmpdir, 'transport.crt') + with open(transport_cert_file, 'w', encoding='utf-8') as f: + f.write(transport_cert) + + install_token = os.path.join(tmpdir, 'install-token') + with open(install_token, 'w', encoding='utf-8') as f: + f.write(self.install_token.token) + + cmd = [ + 'pki', + '-d', self.instance.nssdb_dir, + '-f', self.instance.password_conf, + '-U', ca_url, + '--ignore-banner', + 'ca-kraconnector-add', + '--url', kra_url, + '--subsystem-cert', subsystem_cert_file, + '--transport-cert', transport_cert_file, + '--transport-nickname', transport_nickname, + '--install-token', install_token + ] + + if logger.isEnabledFor(logging.DEBUG): + cmd.append('--debug') + + elif logger.isEnabledFor(logging.INFO): + cmd.append('--verbose') + + logger.debug('Command: %s', ' '.join(cmd)) + subprocess.check_call(cmd) + + finally: + shutil.rmtree(tmpdir) + + def add_ocsp_publisher(self, subsystem, ca_url): + + server_config = self.instance.get_server_config() + hostname = self.mdict['pki_hostname'] + securePort = server_config.get_secure_port() + + ocsp_url = 'https://%s:%s' % (hostname, securePort) + + subsystem_cert = subsystem.get_subsystem_cert('subsystem').get('data') + + tmpdir = tempfile.mkdtemp() + try: + subsystem_cert_file = os.path.join(tmpdir, 'subsystem.crt') + with open(subsystem_cert_file, 'w', encoding='utf-8') as f: + f.write(subsystem_cert) + + install_token = os.path.join(tmpdir, 'install-token') + with open(install_token, 'w', encoding='utf-8') as f: + f.write(self.install_token.token) + + cmd = [ + 'pki', + '-d', self.instance.nssdb_dir, + '-f', self.instance.password_conf, + '-U', ca_url, + '--ignore-banner', + 'ca-publisher-ocsp-add', + '--url', ocsp_url, + '--subsystem-cert', subsystem_cert_file, + '--install-token', install_token + ] + + if logger.isEnabledFor(logging.DEBUG): + cmd.append('--debug') + + elif logger.isEnabledFor(logging.INFO): + cmd.append('--verbose') + + logger.debug('Command: %s', ' '.join(cmd)) + subprocess.check_call(cmd) + + finally: + shutil.rmtree(tmpdir) + + def get_kra_transport_cert(self): + + kra_url = self.mdict['pki_kra_uri'] + + cmd = [ + 'pki', + '-d', self.instance.nssdb_dir, + '-f', self.instance.password_conf, + '-U', kra_url, + '--ignore-banner', + 'kra-cert-transport-export' + ] + + if logger.isEnabledFor(logging.DEBUG): + cmd.append('--debug') + + elif logger.isEnabledFor(logging.INFO): + cmd.append('--verbose') + + logger.debug('Command: %s', ' '.join(cmd)) + result = subprocess.run(cmd, stdout=subprocess.PIPE, check=True) + + return result.stdout.decode() + + def set_tks_transport_cert(self, cert, session=None, install_token=None): + + tks_url = self.mdict['pki_tks_uri'] + sd_url = self.mdict['pki_security_domain_uri'] + + hostname = self.mdict['pki_hostname'] + + server_config = self.instance.get_server_config() + secure_port = server_config.get_secure_port() + + nickname = 'transportCert-%s-%s' % (hostname, secure_port) + + tmpdir = tempfile.mkdtemp() + try: + if not install_token: + install_token = os.path.join(tmpdir, 'install-token') + with open(install_token, 'w', encoding='utf-8') as f: + f.write(session) + + cmd = [ + 'pki', + '-d', self.instance.nssdb_dir, + '-f', self.instance.password_conf, + '-U', tks_url, + '--ignore-banner', + 'tks-cert-transport-import', + '--security-domain', sd_url, + '--install-token', install_token, + nickname + ] + + if logger.isEnabledFor(logging.DEBUG): + cmd.append('--debug') + + elif logger.isEnabledFor(logging.INFO): + cmd.append('--verbose') + + logger.debug('Command: %s', ' '.join(cmd)) + + # don't use text param to support Python 3.6 + # https://stackoverflow.com/questions/52663518/python-subprocess-popen-doesnt-take-text-argument + + subprocess.run( + cmd, + input=cert, + universal_newlines=True, + check=True) + + finally: + shutil.rmtree(tmpdir) + + def get_tps_connector(self, subsystem): + + tks_uri = self.mdict['pki_tks_uri'] + subsystem_cert = subsystem.get_subsystem_cert('subsystem') + + nickname = subsystem_cert['nickname'] + token = subsystem_cert['token'] + + if not pki.nssdb.internal_token(token): + nickname = token + ':' + nickname + + server_config = self.instance.get_server_config() + securePort = server_config.get_secure_port() + + cmd = [ + 'pki', + '-U', tks_uri, + '-d', self.instance.nssdb_dir, + '-f', self.instance.password_conf, + '-n', nickname, + '--ignore-banner', + 'tks-tpsconnector-show', + '--host', self.mdict['pki_hostname'], + '--port', securePort + ] + + logger.debug('Command: %s', ' '.join(cmd)) + result = subprocess.run(cmd, stdout=subprocess.PIPE, check=False) + + if result.returncode == 0: + return json.loads(result.stdout.decode()) + else: + return None + + def create_tps_connector(self, subsystem): + + tks_uri = self.mdict['pki_tks_uri'] + subsystem_cert = subsystem.get_subsystem_cert('subsystem') + + nickname = subsystem_cert['nickname'] + token = subsystem_cert['token'] + + if not pki.nssdb.internal_token(token): + nickname = token + ':' + nickname + + server_config = self.instance.get_server_config() + securePort = server_config.get_secure_port() + + cmd = [ + 'pki', + '-U', tks_uri, + '-d', self.instance.nssdb_dir, + '-f', self.instance.password_conf, + '-n', nickname, + '--ignore-banner', + 'tks-tpsconnector-add', + '--host', self.mdict['pki_hostname'], + '--port', securePort, + '--output-format', 'json' + ] + + logger.debug('Command: %s', ' '.join(cmd)) + result = subprocess.run(cmd, stdout=subprocess.PIPE, check=False) + + if result.returncode == 0: + return json.loads(result.stdout.decode()) + else: + return None + + def get_shared_secret(self, subsystem, tps_connector_id): + + tks_uri = self.mdict['pki_tks_uri'] + subsystem_cert = subsystem.get_subsystem_cert('subsystem') + + nickname = subsystem_cert['nickname'] + token = subsystem_cert['token'] + + if not pki.nssdb.internal_token(token): + nickname = token + ':' + nickname + + cmd = [ + 'pki', + '-U', tks_uri, + '-d', self.instance.nssdb_dir, + '-f', self.instance.password_conf, + '-n', nickname, + '--ignore-banner', + 'tks-key-export', tps_connector_id + ] + + logger.debug('Command: %s', ' '.join(cmd)) + result = subprocess.run(cmd, stdout=subprocess.PIPE, check=False) + + if result.returncode == 0: + return json.loads(result.stdout.decode()) + else: + return None + + def create_shared_secret(self, subsystem, tps_connector_id): + + tks_uri = self.mdict['pki_tks_uri'] + subsystem_cert = subsystem.get_subsystem_cert('subsystem') + + nickname = subsystem_cert['nickname'] + token = subsystem_cert['token'] + + if not pki.nssdb.internal_token(token): + nickname = token + ':' + nickname + + cmd = [ + 'pki', + '-U', tks_uri, + '-d', self.instance.nssdb_dir, + '-f', self.instance.password_conf, + '-n', nickname, + '--ignore-banner', + 'tks-key-create', tps_connector_id, + '--output-format', 'json' + ] + + logger.debug('Command: %s', ' '.join(cmd)) + result = subprocess.run(cmd, stdout=subprocess.PIPE, check=False) + + if result.returncode == 0: + return json.loads(result.stdout.decode()) + else: + return None + + def replace_shared_secret(self, subsystem, tps_connector_id): + + tks_uri = self.mdict['pki_tks_uri'] + subsystem_cert = subsystem.get_subsystem_cert('subsystem') + + nickname = subsystem_cert['nickname'] + token = subsystem_cert['token'] + + if not pki.nssdb.internal_token(token): + nickname = token + ':' + nickname + + cmd = [ + 'pki', + '-U', tks_uri, + '-d', self.instance.nssdb_dir, + '-f', self.instance.password_conf, + '-n', nickname, + '--ignore-banner', + 'tks-key-replace', tps_connector_id, + '--output-format', 'json' + ] + + logger.debug('Command: %s', ' '.join(cmd)) + result = subprocess.run(cmd, stdout=subprocess.PIPE, check=False) + + if result.returncode == 0: + return json.loads(result.stdout.decode()) + else: + return None + + def import_shared_secret(self, subsystem, secret_nickname, shared_secret): + + subsystem_cert = subsystem.get_subsystem_cert('subsystem') + + nickname = subsystem_cert['nickname'] + token = subsystem_cert['token'] + + if not pki.nssdb.internal_token(token): + nickname = token + ':' + nickname + + cmd = [ + 'pki', + '-d', self.instance.nssdb_dir, + '-f', self.instance.password_conf, + 'nss-key-import', secret_nickname, + '--wrapper', nickname + ] + + logger.debug('Command: %s', ' '.join(cmd)) + + # don't use text param to support Python 3.6 + # https://stackoverflow.com/questions/52663518/python-subprocess-popen-doesnt-take-text-argument + + subprocess.run( + cmd, + input=json.dumps(shared_secret), + universal_newlines=True, + check=True) + + def setup_shared_secret(self, subsystem): + + # This method configures the shared secret between TKS and TPS. The shared secret + # is initially generated in TKS, then exported from TKS, and reimported into TPS. + # However, if TKS and TPS are running on the same instance, it is not necessary + # to export and reimport since they are sharing the same NSS database. + + # TODO: Clean up the code and determine whether TKS and TPS are in the same + # instance automatically. + + hostname = self.mdict['pki_hostname'] + + server_config = self.instance.get_server_config() + securePort = server_config.get_secure_port() + + secret_nickname = 'TPS-%s-%s sharedSecret' % (hostname, securePort) + + logger.info('Searching for TPS connector in TKS') + tps_connector = self.get_tps_connector(subsystem) + + if tps_connector: + logger.info('Getting shared secret') + tps_connector_id = tps_connector['id'] + shared_secret = self.get_shared_secret(subsystem, tps_connector_id) + + if shared_secret: + logger.info('Replacing shared secret') + shared_secret = self.replace_shared_secret(subsystem, tps_connector_id) + + else: + logger.info('Creating shared secret') + shared_secret = self.create_shared_secret(subsystem, tps_connector_id) + + else: + logger.info('Creating a new TPS connector') + tps_connector = self.create_tps_connector(subsystem) + tps_connector_id = tps_connector['id'] + + logger.info('Creating shared secret') + shared_secret = self.create_shared_secret(subsystem, tps_connector_id) + + if config.str2bool(self.mdict['pki_import_shared_secret']): + logger.info('Importing shared secret') + self.import_shared_secret(subsystem, secret_nickname, shared_secret) + + subsystem.set_config('conn.tks1.tksSharedSymKeyName', secret_nickname) + subsystem.save() + + def finalize_ca(self, subsystem): + + if config.str2bool(self.mdict['pki_master_crl_enable']): + logger.info('Enabling CRL') + subsystem.set_config('ca.crl.MasterCRL.enable', 'true') + else: + logger.info('Disabling CRL') + subsystem.set_config('ca.crl.MasterCRL.enable', 'false') + + clone = self.configuration_file.clone + + if clone: + logger.info('Disabling CRL caching and generation on clone') + + subsystem.set_config('ca.certStatusUpdateInterval', '0') + subsystem.set_config('ca.listenToCloneModifications', 'false') + subsystem.set_config('ca.crl.MasterCRL.enableCRLCache', 'false') + subsystem.set_config('ca.crl.MasterCRL.enableCRLUpdates', 'false') + + master_url = self.mdict['pki_clone_uri'] + url = urllib.parse.urlparse(master_url) + + subsystem.set_config('master.ca.agent.host', url.hostname) + subsystem.set_config('master.ca.agent.port', str(url.port)) + + else: + logger.info('Updating CA ranges') + subsystem.update_ranges() + + crl_number = self.mdict['pki_ca_starting_crl_number'] + logger.info('Starting CRL number: %s', crl_number) + subsystem.set_config('ca.crl.MasterCRL.startingCrlNumber', crl_number) + + logger.info('Enabling profile subsystem') + subsystem.enable_subsystem('profile') + + # Delete CA signing cert record to avoid migration conflict + if not config.str2bool(self.mdict['pki_ca_signing_record_create']): + logger.info('Deleting CA signing cert record') + serial_number = self.mdict['pki_ca_signing_serial_number'] + subsystem.remove_cert(serial_number) + + def finalize_kra(self, subsystem): + + ca_type = subsystem.config.get('preop.ca.type') + + if ca_type: + subsystem.set_config('cloning.ca.type', ca_type) + + clone = self.configuration_file.clone + standalone = self.configuration_file.standalone + + if not clone: + logger.info('Updating KRA ranges') + subsystem.update_ranges() + + if standalone: + ca_url = None + else: + ca_url = self.mdict['pki_issuing_ca'] + + if ca_url and not clone: + + url = urllib.parse.urlparse(ca_url) + ca_host = url.hostname + ca_port = str(url.port) + + ca_uid = 'CA-%s-%s' % (ca_host, ca_port) + + subsystem.add_user( + ca_uid, + full_name=ca_uid, + user_type='agentType', + state='1', + ignore_duplicate=True) + + logger.info('Getting CA subsystem certificate from %s', ca_url) + subsystem_cert_data = self.get_ca_subsystem_cert(ca_url) + + logger.info('Adding CA subsystem certificate into %s', ca_uid) + subsystem.add_user_cert( + ca_uid, + cert_data=subsystem_cert_data, + cert_format='PEM', + ignore_duplicate=True) + + logger.info('Adding %s into Trusted Managers', ca_uid) + subsystem.add_group_member('Trusted Managers', ca_uid) + + if ca_url: + + logger.info('Adding KRA connector in CA') + self.add_kra_connector(subsystem, ca_url) + + def finalize_ocsp(self, subsystem): + + ca_type = subsystem.config.get('preop.ca.type') + + if ca_type: + subsystem.set_config('cloning.ca.type', ca_type) + + clone = self.configuration_file.clone + standalone = self.configuration_file.standalone + + if clone or standalone: + return + + ca_url = self.mdict['pki_issuing_ca'] + if not ca_url: + return + + tmpdir = tempfile.mkdtemp() + nssdb = self.instance.open_nssdb() + try: + nickname = self.mdict['pki_ocsp_signing_nickname'] + logger.info('Loading OCSP signing PKCS #7: %s', nickname) + + # get OCSP signing PKCS #7 + ocsp_signing_pkcs7 = nssdb.export_pkcs7(nickname) + logger.debug('OCSP signing PKCS #7:\n%s', ocsp_signing_pkcs7) + + # get cert chain from OCSP signing PKCS #7 + cert_chain = nssdb.get_pkcs7_certs(pkcs7_data=ocsp_signing_pkcs7) + + # remove leaf cert to create CA signing cert chain + del cert_chain[-1] + + logger.info('Creating CA signing PKCS #7') + ca_signing_pkcs7 = nssdb.create_pkcs7( + cert_chain=cert_chain, + cert_format='PEM') + + logger.info('Adding CRL issuing point') + subsystem.add_crl_issuing_point( + cert_chain=ca_signing_pkcs7.encode('utf-8'), + cert_format='PEM', + ignore_duplicate=True) + + url = urllib.parse.urlparse(ca_url) + ca_host = url.hostname + ca_port = str(url.port) + + ca_uid = 'CA-%s-%s' % (ca_host, ca_port) + + subsystem.add_user( + ca_uid, + full_name=ca_uid, + user_type='agentType', + state='1', + ignore_duplicate=True) + + logger.info('Getting CA subsystem certificate from %s', ca_url) + subsystem_cert_data = self.get_ca_subsystem_cert(ca_url) + + logger.info('Adding CA subsystem certificate into %s', ca_uid) + subsystem.add_user_cert( + ca_uid, + cert_data=subsystem_cert_data, + cert_format='PEM', + ignore_duplicate=True) + + logger.info('Adding %s into Trusted Managers', ca_uid) + subsystem.add_group_member('Trusted Managers', ca_uid) + + logger.info('Adding OCSP publisher in CA') + # For now don't register publishing with the CA for a clone, + # preserving existing functionality. + # Next we need to treat the publishing of clones as a group, + # and fail over amongst them. + self.add_ocsp_publisher(subsystem, ca_url) + + finally: + nssdb.close() + shutil.rmtree(tmpdir) + + def finalize_tks(self, subsystem): + + ca_type = subsystem.config.get('preop.ca.type') + + if ca_type: + subsystem.set_config('cloning.ca.type', ca_type) + + def finalize_tps(self, subsystem): + + ca_type = subsystem.config.get('preop.ca.type') + + if ca_type: + subsystem.set_config('cloning.ca.type', ca_type) + + tps_uid = 'TPS-%s-%s' % (self.mdict['pki_hostname'], self.mdict['pki_https_port']) + full_name = self.mdict['pki_subsystem_name'] + subsystem_cert = subsystem.get_subsystem_cert('subsystem').get('data') + + if self.mdict['pki_ca_uri']: + + logger.info('Registering TPS in CA') + self.add_subsystem_user( + 'ca', + self.mdict['pki_ca_uri'], + tps_uid, + full_name, + cert=subsystem_cert, + session=self.install_token.token) + + if self.mdict['pki_tks_uri']: + + logger.info('Registering TPS in TKS') + self.add_subsystem_user( + 'tks', + self.mdict['pki_tks_uri'], + tps_uid, + full_name, + cert=subsystem_cert, + session=self.install_token.token) + + keygen = config.str2bool(self.mdict['pki_enable_server_side_keygen']) + + if self.mdict['pki_kra_uri'] and keygen: + + logger.info('Registering TPS in KRA') + self.add_subsystem_user( + 'kra', + self.mdict['pki_kra_uri'], + tps_uid, + full_name, + cert=subsystem_cert, + session=self.install_token.token) + + logger.info('Exporting transport cert from KRA') + transport_cert = self.get_kra_transport_cert() + + logger.info('Importing transport cert into TKS') + self.set_tks_transport_cert( + transport_cert, + session=self.install_token.token) + + if self.mdict['pki_tks_uri']: + + logger.info('Setting up shared secret') + self.setup_shared_secret(subsystem) + + def finalize_subsystem(self, subsystem): + + if subsystem.type == 'CA': + self.finalize_ca(subsystem) + + if subsystem.type == 'KRA': + self.finalize_kra(subsystem) + + if subsystem.type == 'OCSP': + self.finalize_ocsp(subsystem) + + if subsystem.type == 'TKS': + self.finalize_tks(subsystem) + + if subsystem.type == 'TPS': + self.finalize_tps(subsystem) + + # save EC type for sslserver cert (if present) + ec_type = subsystem.config.get('preop.cert.sslserver.ec.type', 'ECDHE') + subsystem.set_config('jss.ssl.sslserver.ectype', ec_type) + + for key in list(subsystem.config.keys()): + if key.startswith('preop.'): + del subsystem.config[key] + + subsystem.set_config('cs.state', '1') + + subsystem.save() + + def store_config(self): + + subsystem = self.instance.get_subsystem(self.subsystem_type.lower()) + + # Store user's deployment.cfg into + # /etc/sysconfig/pki/tomcat///deployment.cfg + + deployment_cfg = os.path.join(subsystem.registry_dir, 'deployment.cfg') + logger.info('Creating %s', deployment_cfg) + + self.file.create(deployment_cfg) + + with open(deployment_cfg, 'w', encoding='utf-8') as f: + self.user_config.write(f) + + # For debugging/auditing purposes, store user's deployment.cfg into + # /var/lib/pki//logs//archive/spawn_deployment.cfg. + + deployment_cfg_archive = os.path.join( + subsystem.log_archive_dir, + 'spawn_deployment.cfg.' + self.mdict['pki_timestamp']) + logger.info('Creating %s', deployment_cfg_archive) + + self.file.copy(deployment_cfg, deployment_cfg_archive) + self.instance.chown(deployment_cfg_archive) + + def set_systemd_override(self, section, param, value, fname='local.conf'): + if fname not in self.systemd.overrides: + parser = configparser.ConfigParser() + parser.optionxform = str + override_file = os.path.join(self.systemd.override_dir, fname) + if os.path.exists(override_file): + parser.read(override_file) + self.systemd.overrides[fname] = parser + else: + parser = self.systemd.overrides[fname] + + if not parser.has_section(section): + parser.add_section(section) + + parser.set(section, param, value) + + def write_systemd_overrides(self): + for fname, parser in self.systemd.overrides.items(): + + override_file = os.path.join(self.systemd.override_dir, fname) + + if not os.path.exists(override_file): + + self.directory.create( + self.systemd.override_dir, + uid=0, + gid=0) + + self.file.create( + os.path.join(self.systemd.override_dir, override_file), + uid=0, + gid=0) + + with open(override_file, 'w', encoding='utf-8') as fp: + parser.write(fp) + + def store_manifest(self): + + subsystem = self.instance.get_subsystem(self.subsystem_type.lower()) + + # Store installation manifest into + # /etc/sysconfig/pki/tomcat///manifest + + manifest_file = os.path.join(subsystem.registry_dir, 'manifest') + logger.info('Creating %s', manifest_file) + + file = manifest.File(self.manifest_db) + file.register(manifest_file) + file.write() + + self.file.modify(manifest_file, silent=True) + + # For debugging/auditing purposes, store installation manifest into + # /var/lib/pki//logs//archive/spawn_manifest. + + manifest_archive = os.path.join( + subsystem.log_archive_dir, + 'spawn_manifest.' + self.mdict['pki_timestamp']) + logger.info('Creating %s', manifest_archive) + + self.file.copy(manifest_file, manifest_archive) + self.instance.chown(manifest_archive) + + def restore_selinux_contexts(self): + + selinux.restorecon(self.instance.base_dir, True) + selinux.restorecon(config.PKI_DEPLOYMENT_LOG_ROOT, True) + selinux.restorecon(self.instance.actual_logs_dir, True) + selinux.restorecon(self.instance.actual_conf_dir, True) + + def selinux_context_exists(self, records, context_value): + ''' + Check if a given `context_value` exists in the given set of `records`. + This method can process both port contexts and file contexts. + ''' + for keys in records.keys(): + for key in keys: + if str(key) == context_value: + return True + return False + + def create_selinux_contexts(self): + + suffix = '(/.*)?' + + trans = seobject.semanageRecords('targeted') + trans.start() + + fcon = seobject.fcontextRecords(trans) + + logger.info('Adding SELinux fcontext "%s"', self.instance.actual_conf_dir + suffix) + fcon.add( + self.instance.actual_conf_dir + suffix, + config.PKI_CFG_SELINUX_CONTEXT, '', 's0', '') + + logger.info('Adding SELinux fcontext "%s"', self.instance.nssdb_dir + suffix) + fcon.add( + self.instance.nssdb_dir + suffix, + config.PKI_CERTDB_SELINUX_CONTEXT, '', 's0', '') + + logger.info('Adding SELinux fcontext "%s"', self.instance.base_dir + suffix) + fcon.add( + self.instance.base_dir + suffix, + config.PKI_INSTANCE_SELINUX_CONTEXT, '', 's0', '') + + logger.info('Adding SELinux fcontext "%s"', self.instance.actual_logs_dir + suffix) + fcon.add( + self.instance.actual_logs_dir + suffix, + config.PKI_LOG_SELINUX_CONTEXT, '', 's0', '') + + port_records = seobject.portRecords(trans) + + for port in config.pki_selinux_config_ports: + logger.info('Adding SELinux port %s', port) + port_records.add( + port, 'tcp', 's0', + config.PKI_PORT_SELINUX_CONTEXT) + + trans.finish() + + def remove_selinux_contexts(self): + + suffix = '(/.*)?' + + trans = seobject.semanageRecords('targeted') + trans.start() + + port_records = seobject.portRecords(trans) + port_record_values = port_records.get_all() + + for port in config.pki_selinux_config_ports: + if self.selinux_context_exists(port_record_values, port): + logger.info('Removing SELinux port %s', port) + port_records.delete(port, 'tcp') + + fcon = seobject.fcontextRecords(trans) + file_records = fcon.get_all() + + if self.selinux_context_exists(file_records, self.instance.actual_logs_dir + suffix): + logger.info('Removing SELinux fcontext "%s"', self.instance.actual_logs_dir + suffix) + fcon.delete(self.instance.actual_logs_dir + suffix, '') + + if self.selinux_context_exists(file_records, self.instance.base_dir + suffix): + logger.info('Removing SELinux fcontext "%s"', self.instance.base_dir + suffix) + fcon.delete(self.instance.base_dir + suffix, '') + + if self.selinux_context_exists(file_records, self.instance.nssdb_dir + suffix): + logger.info('Removing SELinux fcontext "%s"', self.instance.nssdb_dir + suffix) + fcon.delete(self.instance.nssdb_dir + suffix, '') + + if self.selinux_context_exists(file_records, self.instance.actual_conf_dir + suffix): + logger.info('Removing SELinux fcontext "%s"', self.instance.actual_conf_dir + suffix) + fcon.delete(self.instance.actual_conf_dir + suffix, '') + + trans.finish() + + def create_acme_subsystem(self): + ''' + See also pki-server acme-create. + ''' + + logger.info('Creating ACME subsystem') + + subsystem = pki.server.subsystem.ACMESubsystem(self.instance) + subsystem.create() + + return subsystem + + def configure_acme_database(self, subsystem): + ''' + See also pki-server acme-database-mod. + ''' + + logger.info('Configuring ACME database') + + database_type = self.mdict['acme_database_type'] + props = subsystem.get_database_config(database_type=database_type) + + database_class = pki.server.subsystem.ACME_DATABASE_CLASSES.get(database_type) + pki.util.set_property(props, 'class', database_class) + + if database_type in ['ds', 'ldap', 'openldap']: + + url = self.mdict.get('acme_database_url') + pki.util.set_property(props, 'url', url) + + auth_type = props.get('authType') + auth_type = self.mdict.get('acme_database_auth_type', auth_type) + pki.util.set_property(props, 'authType', auth_type) + + if auth_type == 'BasicAuth': + bind_dn = self.mdict.get('acme_database_bind_dn') + pki.util.set_property(props, 'bindDN', bind_dn) + + bind_password = self.mdict.get('acme_database_bind_password') + pki.util.set_property(props, 'bindPassword', bind_password) + + elif auth_type == 'SslClientAuth': + nickname = self.mdict.get('acme_database_nickname') + pki.util.set_property(props, 'nickname', nickname) + + base_dn = self.mdict.get('acme_database_base_dn') + pki.util.set_property(props, 'baseDN', base_dn) + + elif database_type == 'postgresql': + + url = self.mdict.get('acme_database_url') + pki.util.set_property(props, 'url', url) + + user = self.mdict.get('acme_database_user') + pki.util.set_property(props, 'user', user) + + password = self.mdict.get('acme_database_password') + pki.util.set_property(props, 'password', password) + + subsystem.update_database_config(props) + + def configure_acme_issuer(self, subsystem): + ''' + See also pki-server acme-issuer-mod. + ''' + + logger.info('Configuring ACME issuer') + + issuer_type = self.mdict['acme_issuer_type'] + props = subsystem.get_issuer_config(issuer_type=issuer_type) + + issuer_class = pki.server.subsystem.ACME_ISSUER_CLASSES.get(issuer_type) + pki.util.set_property(props, 'class', issuer_class) + + if issuer_type == 'nss': + + nickname = self.mdict.get('acme_issuer_nickname') + pki.util.set_property(props, 'nickname', nickname) + + extensions = self.mdict.get('acme_issuer_extensions') + pki.util.set_property(props, 'extensions', extensions) + + elif issuer_type == 'pki': + + url = self.mdict.get('acme_issuer_url') + pki.util.set_property(props, 'url', url) + + nickname = self.mdict.get('acme_issuer_nickname') + pki.util.set_property(props, 'nickname', nickname) + + username = self.mdict.get('acme_issuer_username') + pki.util.set_property(props, 'username', username) + + password = self.mdict.get('acme_issuer_password') + pki.util.set_property(props, 'password', password) + + password_file = self.mdict.get('acme_issuer_password_file') + pki.util.set_property(props, 'passwordFile', password_file) + + profile = self.mdict.get('acme_issuer_profile') + pki.util.set_property(props, 'profile', profile) + + subsystem.update_issuer_config(props) + + def configure_acme_realm(self, subsystem): + ''' + See also pki-server acme-realm-mod. + ''' + + logger.info('Configuring ACME realm') + + realm_type = self.mdict['acme_realm_type'] + props = subsystem.get_realm_config(realm_type=realm_type) + + realm_class = pki.server.subsystem.ACME_REALM_CLASSES.get(realm_type) + pki.util.set_property(props, 'class', realm_class) + + if realm_type == 'in-memory': + + username = self.mdict.get('acme_realm_username') + pki.util.set_property(props, 'username', username) + + password = self.mdict.get('acme_realm_password') + pki.util.set_property(props, 'password', password) + + elif realm_type == 'ds': + + url = self.mdict.get('acme_realm_url') + pki.util.set_property(props, 'url', url) + + auth_type = props.get('authType') + auth_type = self.mdict.get('acme_realm_auth_type', auth_type) + pki.util.set_property(props, 'authType', auth_type) + + if auth_type == 'BasicAuth': + bind_dn = self.mdict.get('acme_realm_bind_dn') + pki.util.set_property(props, 'bindDN', bind_dn) + + bind_password = self.mdict.get('acme_realm_bind_password') + pki.util.set_property(props, 'bindPassword', bind_password) + + elif auth_type == 'SslClientAuth': + nickname = self.mdict.get('acme_realm_nickname') + pki.util.set_property(props, 'nickname', nickname) + + users_dn = self.mdict.get('acme_realm_users_dn') + pki.util.set_property(props, 'usersDN', users_dn) + + groups_dn = self.mdict.get('acme_realm_groups_dn') + pki.util.set_property(props, 'groupsDN', groups_dn) + + elif realm_type == 'postgresql': + + url = self.mdict.get('acme_realm_url') + pki.util.set_property(props, 'url', url) + + user = self.mdict.get('acme_realm_user') + pki.util.set_property(props, 'user', user) + + password = self.mdict.get('acme_realm_password') + pki.util.set_property(props, 'password', password) + + subsystem.update_realm_config(props) + + def deploy_acme_webapp(self, subsystem): + ''' + See also pki-server acme-deploy. + ''' + + logger.info('Deploying ACME webapp') + + if len(self.instance.get_subsystems()) == 1: + # if this is the first subsystem, deploy the subsystem without waiting + subsystem.enable() + + else: + # otherwise, deploy the subsystem and wait until it starts + subsystem.enable( + wait=True, + max_wait=self.startup_timeout, + timeout=self.request_timeout) + + + def spawn_acme(self): + + subsystem = self.create_acme_subsystem() + self.instance.add_subsystem(subsystem) + + self.configure_acme_database(subsystem) + self.configure_acme_issuer(subsystem) + self.configure_acme_realm(subsystem) + + self.deploy_acme_webapp(subsystem) + + def create_est_subsystem(self): + ''' + See also pki-server acme-create. + ''' + + logger.info('Creating EST subsystem') + + subsystem = pki.server.subsystem.ESTSubsystem(self.instance) + subsystem.create() + return subsystem + + def configure_est_backend(self, subsystem): + logger.info('Configuring EST backend') + props = subsystem.get_backend_config(True) + ca_uri = self.mdict.get('pki_ca_uri') + pki.util.set_property(props, 'url', ca_uri) + + profile = self.mdict.get('est_ca_profile') + pki.util.set_property(props, 'profile', profile) + + username = self.mdict.get('est_ca_user_name') + pki.util.set_property(props, 'username', username) + + password = self.mdict.get('est_ca_user_password') + pki.util.set_property(props, 'password', password) + + password_file = self.mdict.get('est_ca_user_password_file') + pki.util.set_property(props, 'passwordFile', password_file) + + nickname = self.mdict.get('est_ca_user_certificate') + pki.util.set_property(props, 'nickname', nickname) + subsystem.update_backend_config(props) + + def configure_est_authorizer(self, subsystem): + + logger.info('Configuring EST authorizer') + props = subsystem.get_authorizer_config(True) + + authorizer_exec = self.mdict.get('OBest_authorizer_exec_path') + pki.util.set_property(props, 'executable', authorizer_exec) + subsystem.update_authorizer_config(props) + + def configure_est_realm(self, subsystem): + logger.info('Configuring EST realm') + + if self.mdict['est_realm_custom']: + subsystem.replace_realm_config(self.mdict['est_realm_custom']) + return + + realm_type = self.mdict['est_realm_type'] + props = subsystem.get_realm_config(realm_type=realm_type) + + if realm_type == 'in-memory': + + username = self.mdict.get('est_realm_username') + pki.util.set_property(props, 'username', username) + + password = self.mdict.get('est_realm_password') + pki.util.set_property(props, 'password', password) + + elif realm_type == 'ds': + + url = self.mdict.get('est_realm_url') + pki.util.set_property(props, 'url', url) + + auth_type = props.get('authType') + auth_type = self.mdict.get('est_realm_auth_type', auth_type) + pki.util.set_property(props, 'authType', auth_type) + + if auth_type == 'BasicAuth': + bind_dn = self.mdict.get('est_realm_bind_dn') + pki.util.set_property(props, 'bindDN', bind_dn) + + bind_password = self.mdict.get('est_realm_bind_password') + pki.util.set_property(props, 'bindPassword', bind_password) + + elif auth_type == 'SslClientAuth': + nickname = self.mdict.get('est_realm_nickname') + pki.util.set_property(props, 'nickname', nickname) + + users_dn = self.mdict.get('est_realm_users_dn') + pki.util.set_property(props, 'usersDN', users_dn) + + groups_dn = self.mdict.get('est_realm_groups_dn') + pki.util.set_property(props, 'groupsDN', groups_dn) + + elif realm_type == 'postgresql': + + url = self.mdict.get('est_realm_url') + pki.util.set_property(props, 'url', url) + + user = self.mdict.get('est_realm_user') + pki.util.set_property(props, 'user', user) + + password = self.mdict.get('est_realm_password') + pki.util.set_property(props, 'password', password) + + statements = self.mdict.get('est_realm_statements') + pki.util.set_property(props, 'statements', statements) + subsystem.update_realm_config(props) + + + def deploy_est_webapp(self, subsystem): + ''' + See also pki-server est-deploy. + ''' + + logger.info('Deploying EST webapp') + if len(self.instance.get_subsystems()) == 1: + # if this is the first subsystem, deploy the subsystem without waiting + subsystem.enable() + + else: + # otherwise, deploy the subsystem and wait until it starts + subsystem.enable( + wait=True, + max_wait=self.startup_timeout, + timeout=self.request_timeout) + + def spawn_est(self): + + subsystem = self.create_est_subsystem() + self.instance.add_subsystem(subsystem) + + self.configure_est_backend(subsystem) + self.configure_est_authorizer(subsystem) + self.configure_est_realm(subsystem) + + self.deploy_est_webapp(subsystem) + + + def spawn(self): + + print('Installing ' + self.subsystem_type + ' into ' + self.instance.base_dir + '.') + + scriptlet = pki.server.deployment.scriptlets.initialization.PkiScriptlet() + scriptlet.deployer = self + scriptlet.instance = self.instance + scriptlet.spawn(self) + + scriptlet = pki.server.deployment.scriptlets.infrastructure_layout.PkiScriptlet() + scriptlet.deployer = self + scriptlet.instance = self.instance + scriptlet.spawn(self) + + scriptlet = pki.server.deployment.scriptlets.instance_layout.PkiScriptlet() + scriptlet.deployer = self + scriptlet.instance = self.instance + scriptlet.spawn(self) + + if self.subsystem_type == 'ACME': + self.spawn_acme() + return + + if self.subsystem_type == 'EST': + self.spawn_est() + return + + scriptlet = pki.server.deployment.scriptlets.subsystem_layout.PkiScriptlet() + scriptlet.deployer = self + scriptlet.instance = self.instance + scriptlet.spawn(self) + + scriptlet = pki.server.deployment.scriptlets.security_databases.PkiScriptlet() + scriptlet.deployer = self + scriptlet.instance = self.instance + scriptlet.spawn(self) + + scriptlet = pki.server.deployment.scriptlets.selinux_setup.PkiScriptlet() + scriptlet.deployer = self + scriptlet.instance = self.instance + scriptlet.spawn(self) + + scriptlet = pki.server.deployment.scriptlets.keygen.PkiScriptlet() + scriptlet.deployer = self + scriptlet.instance = self.instance + scriptlet.spawn(self) + + scriptlet = pki.server.deployment.scriptlets.fapolicy_setup.PkiScriptlet() + scriptlet.deployer = self + scriptlet.instance = self.instance + scriptlet.spawn(self) + + scriptlet = pki.server.deployment.scriptlets.configuration.PkiScriptlet() + scriptlet.deployer = self + scriptlet.instance = self.instance + scriptlet.spawn(self) + + scriptlet = pki.server.deployment.scriptlets.finalization.PkiScriptlet() + scriptlet.deployer = self + scriptlet.instance = self.instance + scriptlet.spawn(self) + + def destroy(self): + + print('Uninstalling ' + self.subsystem_type + ' from ' + self.instance.base_dir + '.') + + scriptlet = pki.server.deployment.scriptlets.initialization.PkiScriptlet() + scriptlet.deployer = self + scriptlet.instance = self.instance + scriptlet.destroy(self) + + scriptlet = pki.server.deployment.scriptlets.configuration.PkiScriptlet() + scriptlet.deployer = self + scriptlet.instance = self.instance + scriptlet.destroy(self) + + scriptlet = pki.server.deployment.scriptlets.keygen.PkiScriptlet() + scriptlet.deployer = self + scriptlet.instance = self.instance + scriptlet.destroy(self) + + scriptlet = pki.server.deployment.scriptlets.subsystem_layout.PkiScriptlet() + scriptlet.deployer = self + scriptlet.instance = self.instance + scriptlet.destroy(self) + + scriptlet = pki.server.deployment.scriptlets.security_databases.PkiScriptlet() + scriptlet.deployer = self + scriptlet.instance = self.instance + scriptlet.destroy(self) + + scriptlet = pki.server.deployment.scriptlets.instance_layout.PkiScriptlet() + scriptlet.deployer = self + scriptlet.instance = self.instance + scriptlet.destroy(self) + + scriptlet = pki.server.deployment.scriptlets.selinux_setup.PkiScriptlet() + scriptlet.deployer = self + scriptlet.instance = self.instance + scriptlet.destroy(self) + + scriptlet = pki.server.deployment.scriptlets.infrastructure_layout.PkiScriptlet() + scriptlet.deployer = self + scriptlet.instance = self.instance + scriptlet.destroy(self) + + scriptlet = pki.server.deployment.scriptlets.finalization.PkiScriptlet() + scriptlet.deployer = self + scriptlet.instance = self.instance + scriptlet.destroy(self) + + scriptlet = pki.server.deployment.scriptlets.fapolicy_setup.PkiScriptlet() + scriptlet.deployer = self + scriptlet.instance = self.instance + scriptlet.destroy(self) diff --git a/base/server/python/pki/server/deployment/.#__init__.py b/base/server/python/pki/server/deployment/.#__init__.py new file mode 120000 index 00000000000..0c9351df702 --- /dev/null +++ b/base/server/python/pki/server/deployment/.#__init__.py @@ -0,0 +1 @@ +mfargetta@fedora.992000:1725871187 \ No newline at end of file diff --git a/base/server/python/pki/server/deployment/__init__.py b/base/server/python/pki/server/deployment/__init__.py index 84e189915c8..68d9d523587 100644 --- a/base/server/python/pki/server/deployment/__init__.py +++ b/base/server/python/pki/server/deployment/__init__.py @@ -5365,6 +5365,7 @@ def deploy_acme_webapp(self, subsystem): max_wait=self.startup_timeout, timeout=self.request_timeout) + def spawn_acme(self): subsystem = self.create_acme_subsystem() @@ -5376,13 +5377,137 @@ def spawn_acme(self): self.deploy_acme_webapp(subsystem) + def create_est_subsystem(self): + ''' + See also pki-server acme-create. + ''' + + logger.info('Creating EST subsystem') + + subsystem = pki.server.subsystem.ESTSubsystem(self.instance) + subsystem.create() + return subsystem + + def configure_est_backend(self, subsystem): + logger.info('Configuring EST backend') + props = subsystem.get_backend_config(True) + ca_uri = self.mdict.get('pki_ca_uri') + pki.util.set_property(props, 'url', ca_uri) + + profile = self.mdict.get('est_ca_profile') + pki.util.set_property(props, 'profile', profile) + + username = self.mdict.get('est_ca_user_name') + pki.util.set_property(props, 'username', username) + + password = self.mdict.get('est_ca_user_password') + pki.util.set_property(props, 'password', password) + + password_file = self.mdict.get('est_ca_user_password_file') + pki.util.set_property(props, 'passwordFile', password_file) + + nickname = self.mdict.get('est_ca_user_certificate') + pki.util.set_property(props, 'nickname', nickname) + subsystem.update_backend_config(props) + + def configure_est_authorizer(self, subsystem): + + logger.info('Configuring EST authorizer') + props = subsystem.get_authorizer_config(True) + + authorizer_exec = self.mdict.get('est_authorizer_exec_path') + pki.util.set_property(props, 'executable', authorizer_exec) + subsystem.update_authorizer_config(props) + + def configure_est_realm(self, subsystem): + logger.info('Configuring EST realm') + + if self.mdict['est_realm_custom']: + subsystem.replace_realm_config(self.mdict['est_realm_custom']) + return + + realm_type = self.mdict['est_realm_type'] + props = subsystem.get_realm_config(realm_type=realm_type) + + if realm_type == 'in-memory': + + username = self.mdict.get('est_realm_username') + pki.util.set_property(props, 'username', username) + + password = self.mdict.get('est_realm_password') + pki.util.set_property(props, 'password', password) + + elif realm_type == 'ds': + + url = self.mdict.get('est_realm_url') + pki.util.set_property(props, 'url', url) + + auth_type = props.get('authType') + auth_type = self.mdict.get('est_realm_auth_type', auth_type) + pki.util.set_property(props, 'authType', auth_type) + + if auth_type == 'BasicAuth': + bind_dn = self.mdict.get('est_realm_bind_dn') + pki.util.set_property(props, 'bindDN', bind_dn) + + bind_password = self.mdict.get('est_realm_bind_password') + pki.util.set_property(props, 'bindPassword', bind_password) + + elif auth_type == 'SslClientAuth': + nickname = self.mdict.get('est_realm_nickname') + pki.util.set_property(props, 'nickname', nickname) + + users_dn = self.mdict.get('est_realm_users_dn') + pki.util.set_property(props, 'usersDN', users_dn) + + groups_dn = self.mdict.get('est_realm_groups_dn') + pki.util.set_property(props, 'groupsDN', groups_dn) + + elif realm_type == 'postgresql': + + url = self.mdict.get('est_realm_url') + pki.util.set_property(props, 'url', url) + + user = self.mdict.get('est_realm_user') + pki.util.set_property(props, 'user', user) + + password = self.mdict.get('est_realm_password') + pki.util.set_property(props, 'password', password) + + statements = self.mdict.get('est_realm_statements') + pki.util.set_property(props, 'statements', statements) + subsystem.update_realm_config(props) + + + def deploy_est_webapp(self, subsystem): + ''' + See also pki-server est-deploy. + ''' + + logger.info('Deploying EST webapp') if len(self.instance.get_subsystems()) == 1: - # if this is the first subsystem, start the server - self.instance.start( + # if this is the first subsystem, deploy the subsystem without waiting + subsystem.enable() + + else: + # otherwise, deploy the subsystem and wait until it starts + subsystem.enable( wait=True, max_wait=self.startup_timeout, timeout=self.request_timeout) + def spawn_est(self): + + subsystem = self.create_est_subsystem() + self.instance.add_subsystem(subsystem) + + self.configure_est_backend(subsystem) + self.configure_est_authorizer(subsystem) + self.configure_est_realm(subsystem) + + self.deploy_est_webapp(subsystem) + + def spawn(self): print('Installing ' + self.subsystem_type + ' into ' + self.instance.base_dir + '.') @@ -5406,6 +5531,10 @@ def spawn(self): self.spawn_acme() return + if self.subsystem_type == 'EST': + self.spawn_est() + return + scriptlet = pki.server.deployment.scriptlets.subsystem_layout.PkiScriptlet() scriptlet.deployer = self scriptlet.instance = self.instance diff --git a/base/server/python/pki/server/deployment/pkiconfig.py b/base/server/python/pki/server/deployment/pkiconfig.py index 5793401436b..ae6be596fd8 100644 --- a/base/server/python/pki/server/deployment/pkiconfig.py +++ b/base/server/python/pki/server/deployment/pkiconfig.py @@ -36,7 +36,7 @@ PKI_DEPLOYMENT_DEFAULT_UID = 17 PKI_DEPLOYMENT_DEFAULT_USER = "pkiuser" -PKI_SUBSYSTEMS = ['CA', 'KRA', 'OCSP', 'TKS', 'TPS', 'ACME'] +PKI_SUBSYSTEMS = ['CA', 'KRA', 'OCSP', 'TKS', 'TPS', 'ACME', 'EST'] PKI_BASE_RESERVED_NAMES = ["alias", "bin", "ca", "common", "conf", "kra", "lib", "logs", "ocsp", "temp", "tks", "tps", "webapps", "work"] diff --git a/base/server/python/pki/server/deployment/scriptlets/initialization.py b/base/server/python/pki/server/deployment/scriptlets/initialization.py index 5639b2448ff..4400b1835ab 100644 --- a/base/server/python/pki/server/deployment/scriptlets/initialization.py +++ b/base/server/python/pki/server/deployment/scriptlets/initialization.py @@ -42,7 +42,8 @@ def verify_sensitive_data(self, deployer): # Verify existence of Admin Password (except for Clones) if configuration_file.subsystem != 'ACME' and \ - not configuration_file.clone: + configuration_file.subsystem != 'EST' and not \ + configuration_file.clone: configuration_file.confirm_data_exists('pki_admin_password') # If HSM, verify absence of all PKCS #12 backup parameters @@ -64,7 +65,8 @@ def verify_sensitive_data(self, deployer): configuration_file.confirm_data_exists('pki_client_database_password') # Verify existence of Client PKCS #12 Password for Admin Cert - if configuration_file.subsystem != 'ACME': + if configuration_file.subsystem != 'ACME' and \ + configuration_file.subsystem != 'EST': configuration_file.confirm_data_exists('pki_client_pkcs12_password') if configuration_file.clone: diff --git a/base/server/python/pki/server/pkispawn.py b/base/server/python/pki/server/pkispawn.py index 43c76c38d52..739877d84fa 100644 --- a/base/server/python/pki/server/pkispawn.py +++ b/base/server/python/pki/server/pkispawn.py @@ -190,8 +190,8 @@ def main(argv): parser.indent = 0 deployer.subsystem_type = parser.read_text( - 'Subsystem (CA/KRA/OCSP/TKS/TPS/ACME)', - options=['CA', 'KRA', 'OCSP', 'TKS', 'TPS', 'ACME'], + 'Subsystem (CA/KRA/OCSP/TKS/TPS/ACME/EST)', + options=['CA', 'KRA', 'OCSP', 'TKS', 'TPS', 'ACME', 'EST'], default='CA', case_sensitive=False).upper() print() else: @@ -672,6 +672,9 @@ def main(argv): elif deployer.subsystem_type == 'ACME': print_acme_install_information() + elif deployer.subsystem_type == 'EST': + print_est_install_information() + else: print_final_install_information(parser.mdict, deployer.instance) @@ -693,7 +696,8 @@ def validate_user_deployment_cfg(user_deployment_cfg): '[OCSP]', '[TKS]', '[TPS]', - '[ACME]']: + '[ACME]', + '[EST]']: raise Exception('Invalid deployment configuration section: %s' % line) @@ -951,6 +955,15 @@ def print_acme_install_information(): print(log.PKI_SPAWN_INFORMATION_FOOTER) +def print_est_install_information(): + + print(log.PKI_SPAWN_INFORMATION_HEADER) + + print(log.PKI_ACCESS_URL % (deployer.mdict['pki_hostname'], + deployer.mdict['pki_https_port'], + '.well-known/%s'%deployer.subsystem_type.lower())) + print(log.PKI_SPAWN_INFORMATION_FOOTER) + def print_final_install_information(mdict, instance): diff --git a/base/server/python/pki/server/subsystem.py b/base/server/python/pki/server/subsystem.py index dfa5cc28961..fd356e59e3f 100644 --- a/base/server/python/pki/server/subsystem.py +++ b/base/server/python/pki/server/subsystem.py @@ -2857,6 +2857,108 @@ def update_realm_config(self, config): self.instance.store_properties(self.realm_conf, config) +class ESTSubsystem(PKISubsystem): + + def __init__(self, instance): + super().__init__(instance, 'est') + + @property + def backend_conf(self): + return os.path.join(self.conf_dir, 'backend.conf') + + @property + def authorizer_conf(self): + return os.path.join(self.conf_dir, 'authorizer.conf') + + @property + def realm_conf(self): + return os.path.join(self.conf_dir, 'realm.conf') + + def create(self, exist_ok=False, force=False): + + self.instance.makedirs(self.conf_dir, exist_ok=exist_ok) + + default_conf_dir = os.path.join(pki.server.PKIServer.SHARE_DIR, 'est', 'conf') + + self.instance.copy( + os.path.join(default_conf_dir, 'backend.conf'), + self.backend_conf, + exist_ok=exist_ok, + force=force) + + self.instance.copy( + os.path.join(default_conf_dir, 'authorizer.conf'), + self.authorizer_conf, + exist_ok=exist_ok, + force=force) + + def get_backend_config(self, default=False): + + if default: + backend_conf = os.path.join(pki.server.PKIServer.SHARE_DIR, 'est', 'conf', 'backend.conf') + else: + backend_conf = self.database_conf + + logger.info('Loading %s', backend_conf) + config = {} + pki.util.load_properties(backend_conf, config) + return config + + def update_backend_config(self, config): + + logger.info('Updating %s', self.backend_conf) + self.instance.store_properties(self.backend_conf, config) + + def get_authorizer_config(self, default=False): + + if default: + authorizer_conf = os.path.join(pki.server.PKIServer.SHARE_DIR, 'est', 'conf', 'authorizer.conf') + else: + issuer_conf = self.authorizer_conf + + logger.info('Loading %s', authorizer_conf) + config = {} + pki.util.load_properties(authorizer_conf, config) + + return config + + def update_authorizer_config(self, config): + + logger.info('Updating %s', self.authorizer_conf) + self.instance.store_properties(self.authorizer_conf, config) + + def get_realm_config(self, realm_type=None): + + template_dir = os.path.join(pki.server.PKIServer.SHARE_DIR, 'est', 'conf', 'realm') + + if realm_type: + # if realm type is specified, load the realm.conf template + realm_conf = os.path.join(template_dir, '%s.conf'%realm_type) + else: + # otherwise, load the current realm.conf in the instance + realm_conf = self.realm_conf + + logger.info('Loading %s', realm_conf) + config = {} + pki.util.load_properties(realm_conf, config) + + return config + + def update_realm_config(self, config): + + logger.info('Updating %s', self.realm_conf) + self.instance.store_properties(self.realm_conf, config) + + def replace_realm_config(self, realm_path): + + logger.info('Replace %s', self.realm_conf) + self.instance.copy( + realm_path, + self.realm_conf, + exist_ok=False, + force=True) + + class PKISubsystemFactory(object): @classmethod diff --git a/pki.spec b/pki.spec index e875d88e66a..2029836a1e1 100644 --- a/pki.spec +++ b/pki.spec @@ -1867,6 +1867,7 @@ fi ################################################################################ %{_datadir}/pki/est/ +%{_libexecdir}/estauthz %if %{without maven} %{_datadir}/java/pki/pki-est.jar