This is a PHP implementation of a server-side certificate management protocol (CMP) documented in rfc4210, automatic certificate management environment (ACME), rfc8555, Certificate Enrollment over Secure Transport (EST) defined in rfc7030, online certificate status protocol (OCSP), rfc6960, Certificate Store Access via HTTP, rfc4387, MS-XCEP (https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-XCEP/%5bMS-XCEP%5d.pdf) and MS-WSTEP (https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-WSTEP/%5bMS-WSTEP%5d.pdf).
Fro quick tests and deployment Dockerfile is provided. You may test both sqlite and postgres db at the same time.
git clone https://github.com/creatica-soft/pki
cd pki
# review ARGs in Dockerfile and update as needed
export LDAP_PASSWORD=<ldap_service_account_password>
export PG_PASSWORD=<postgres_password>
docker build -t alpine-pki --rm --secret id=ldap,env=LDAP_PASSWORD --secret id=pg,env=PG_PASSWORD .
unset LDAP_PASSWORD PG_PASSWORD
docker run -it --net=host --name pki alpine-pki
# replace $PKI_DNS and $TEST_DNS with the same values as in Dockerfile
echo 127.0.0.1 $PKI_DNS $TEST_DNS | sudo tee -a /etc/hosts
cd /var/www/pki/cmp_client
php84 tests.php
cd ../est_client
php84 tests.php
cd ../certbot
./tests.sh
sed -i 's/sql_db = "postgres"/sql_db = "sqlite"/' ../lib/config.php
cd /var/www/pki/cmp_client
php84 tests.php
cd ../est_client
php84 tests.php
cd ../certbot
./tests.sh
sed -i 's/sql_db = "sqlite"/sql_db = "postgres"/' ../lib/config.php
For production, /var/pki folder should probably be placed in a docker persistent volume to preserve certificate database in case a new container runs. The same might be done for /var/log, etc. To preserve data in the current container if it stopped, simply start it with
docker contrainer start -i alpine-pki
For a scalable deployment, run each component (postgres, php-fpm, nginx) in a separate kubernetes pod. stunnel is added to nginx and php-fpm as side-car containers to secure communication between nginx and php-fpm. postgres is configured to run over SSL. Ideally, change postgres from a deployment type kubernetes workload to a stateful set and have master-slave replicas. Dockerfiles are provided to for each component to remove dependencies and make it more flexible.
Review Dockerfiles first and make adjustment to ARGs. Build docker images using a command from each Dockerfile (commented first line). Review config.env file and make adjustments to variables. Run create_certs.sh script, which sources config.env to generate root, signing CAs and server (nginx, php, php-fpm, postgres) SSL certificates and create kubernetes secrets and config maps. Start with deploying init workload. It initializes persistent volumes (using docker volumes - adjust as needed): one for postgres database, another for PKI software (php and html files) shared between php-fpm and nginx. Deploy servers in kubernetes using provided YAML manifests. Check the logs. Run tests from init deployment. It can be removed once tested. Note that some tests are meant to fail - this is expected. cmp_client tests normally produce no output if all tests are successful. est_client and certbot produce some output. est_client tests should end with a line saying that all tests succeeded. And it should be no red lines in certbot tests.
docker build -t alpine-init:3.22 --rm -f Dockerfile-init .
docker build -t alpine-fpm:3.22 -f Dockerfile-fpm --rm .
docker build -t alpine-nginx:3.22 -f Dockerfile-nginx --rm .
docker build -t alpine-postgres:3.22 -f Dockerfile-postgres --rm .
docker build -t alpine-stunnel-fpm:3.22 -f Dockerfile-stunnel-fpm --rm .
docker build -t alpine-stunnel-nginx:3.22 -f Dockerfile-stunnel-nginx --rm .
chmod 755 create_certs.sh
./create_certs.sh
kubectl apply -f init.yaml
kubectl logs -l app=init --tail=-1
kubectl apply -f postgres.yaml
kubectl logs -l app=postgres --tail=-1
kubectl apply -f php-fpm.yaml
kubectl logs -l app=php-fpm --tail=-1
kubectl apply -f nginx.yaml
kubectl logs -l app=nginx --tail=-1
kubectl exec -t -i <nginx-pod> -- sh
sudo -u alpine -s
cd /var/www/pki/cmp_client
php84 tests.php
cd ../est_client
php84 tests.php
cd ../certbot
./tests.sh
exit
exit
kubectl delete deployment init
To delete PKI deployment from kubernetes, run cleanup.sh script. It will undo the above steps except docker images won't be removed.
chmod 755 cleanup.sh
./cleanup.sh
To deploy PKI in a cloud VM, ssh to an alpine-based VM in a cloud, install git, clone this repo, review and update vm.env file and execute vm-create-pki.sh. Re-login to update group membership change for alpine user and run tests locally (and remotely). vm-create-pki.sh will attempt to obtain Let's encrypt SSL certificate for your PKI server using it's public DNS name, so that you can access your PKI over Internet with SSL encryption using globally trusted root CA. vm-create-pki.sh script is written to enable only sqlite database, which is sufficient for a single VM. If you really need to run postgres in the same VM, you would need to modify the script. Make sure you expose tcp ports 22, 80 and 443 using security groups.
doas apk add git
pwd
/home/alpine
git clone https://github.com/creatica-soft/pki
cd pki
vi vm.env
chmod 755 vm-create-pki.sh
./vm-create-pki.sh
exit
ssh ...
id
cd /var/www/pki/cmp_client
php84 tests.php
cd ../est_client
php84 tests.php
cd ../certbot
./tests.sh
To uninstall PKI from the VM, change permissions of vm-undo-pki.sh to 755 and run it.
cd /home/alpine/pki
chmod 755 vm-undo-pki.sh
./vm-undo-pki.sh
Openssl version 3 includes RFC4120-compliant CMP client, which has been tested to work with this server. Openssl ocsp client has been tested with OCSP server. Let's Encrypt Certbot has been tested with ACME server. MS Certificates MMC (certmgr.msc and certlm.msc), certutil.exe and certreq.exe have been tested with MS-XCEP and MS-WSTEP.
The client certificate has a subject with a common name (CN) equaled to sAMAccountName (username) and a role equaled to 'standard'.
ACME uses external account binding to associate ACME client public key with its owner via Active Directory account binding.
Standard role in CMP allows requesting key updates and revocations only to certificates issued to the same user, i.e. those that have owner field in their subject equaled to CN of a client certificate. Standard role is not allowed requesting wildcard certificates.
There exists a 'master' role in CMP, which is free from the above limitations.
In ACME clients are free from the above limitations because ACME performs domain validation (DV) and if successful, then issues the certificate.
MS-XCEP is anonymous and MS-WSTEP uses AD LDAP username and password authentication. Default user role is standard. Trusted users can be added to the master role via lib/config.php.
By default MS-XCEP provides three certificate templates (GenericUser, Email and GenericComputer). Additional templates may be added via msxcep/globals.php file.
See index.html
Certificates are stored in sqlite3 or postgres db depending on ARG DB in Dockerfiles. See init-cert.sql and init-acme.sql for sqlite3 schema and createdb.sql for postgres schema.
| File name | PHP Classes | Notes |
|---|---|---|
| lib/algorithm_identifier.php | DHBMParameter, PBMParameter, AlgorithmIdentifier | https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.3 |
| lib/atv.php | AttributeTypeAndValue - used by Name class | https://datatracker.ietf.org/doc/html/rfc4519 |
| lib/base64url.php | base64url_encode() and base64url_decode() | https://datatracker.ietf.org/doc/html/rfc4648#section-5 |
| lib/cert_id.php | CertHash, CertId | Certificate Hash |
| lib/cert_template.php | CertTemplate | https://datatracker.ietf.org/doc/html/rfc4211#section-5 |
| lib/certificate.php | TBSCertificate, Certificate | https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.1 |
| lib/certification_request.php | CertificationRequestInfo, CertificationRequest | https://datatracker.ietf.org/doc/html/rfc2986 |
| lib/extension.php | Extensions, Extension | https://datatracker.ietf.org/doc/html/rfc5280#section-4.2 |
| lib/general_name.php | RDN, Name, GeneralName | https://datatracker.ietf.org/doc/html/rfc4210#section-5.1.1 |
| lib/signed_data.php | ContentInfo, SignedData, SignerInfo | https://datatracker.ietf.org/doc/html/rfc5652 (PKCS #7) |
| lib/subject_pubkey_info.php | RSAPublicKey, DSAPublicKey, DHPublicKey, ECPublicKey, SubjectPublicKeyInfo | https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.7 |
| lib/validity.php | Validity | https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.5 |
| File name | PHP Classes | Notes |
|---|---|---|
| acme/account.php | Account class | https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.2 |
| acme/acme_request.php | ACME request class | https://datatracker.ietf.org/doc/html/rfc8555#section-6.1 |
| acme/authorization.php | Authorization class | https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.4 |
| acme/challenge.php | Challenge class | https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.5 |
| acme/order.php | Order class | https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.3 |
| https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.7 |
| File name | PHP Classes | Notes |
|---|
server.php is the CMP, ACME, EST, OCSP, MS-XCEP or MS-WSTEP server, which answers the CMP, ACME, EST, OCSP, MS-XCEP or MS-WSTEP requests.
lib/asn1_types.php is ITU-T X680/X690 ASN.1 type constants
lib/asn1decode.php - ITU-T X680/X690 ASN.1 decoder, which will get the binary data (DER) and return an array, which is later mapped to CMP class properties.
lib/asn1encode.php - ITU-T X680/X690 ASN.1 encoder, which will take a class, a constructed bit, a type and a value and returns a DER string.
lib/help_functions.php - a bunch of useful functions that are consumed by any file.
lib/sql.php - SQL functions related to certs.db table operations such as select, insert, update and delete lib/acme_sql.php - SQL functions related to acme.db table operations such as select, insert, update and delete lib/cmp_sql.php - SQL functions related to certs.db table operations such as select, insert, update and delete
lib/config.php - this is where all common configuration settings are. globals.php - this is where protocol-specific configuration settings are.
lib/asn1parse.php - very similar to openssl asn1parse, it takes a file in DER or PEM encoding as a first argument and 'pem' as a second if needed.
This is Let's Encrypt server. There are many ACME clients. A client must support external account binding. Many do not. Recommended ACME client is Certbot from Let's Encrypt.
This is a simplified version of both CMP and CMC (rfc5273) protocols. Three well-known URIs are supported: cacerts, simpleenroll and simplereenroll. Certificate revocation, which requires full CMC, is not supported since it is optional in rfc7030. AD username and password as well as SSL client certificate authentication with standard and master roles are supported. Same as in CMP, no domain validation. EST clients do exist but are not required. Certificates can simply be requested using curl and openssl req, base64 and pkcs7 commands. Both openssl versions (1 and 3) work with EST server.
This protocol is used by MS Certificates MMC (certmgr.msc and certlm.msc) to get certificate templates and certificate enrollment url where to submit certification requests (CSR or CMC). This is XML-based protocol, more precisely SOAP 1.2.
This protocol is used by the same Microsoft tools as MS-XCEP to actually request and renew certificates. In theory it should support certificate revocation because it uses full CMC but in practice there is no GUI functions in Certificates MMC to do this; hence, the server side also does not implement certificate revocation.
openssl provides ocsp client, which can be used to verify that OCSP server works fine. Also, openssl verify command may be used.
Well-known URLs are provided for certificates and CRLs:
https://pki.example.org/certificates/search.cgi?attirbute=value
https://pki.example.org/crls/search.cgi?attirbute=value
where all x.509 attributes are supported: certHash, uri, iHash, iAndSHash, name, cn, sHash, sKIDHash.