OpenXPKI

An open, enterprise-grade PKI/Trustcenter

Table of Contents:

Introduction

This manual describes the installation and use of the OpenXPKI software, an Open Source trustcenter solution written by The OpenXPKI Project.

The intended audience are CA administrators and operators. We assume that readers are familiar working on a Unix shell and have enough background knowledge about Public Key Infrastructures to understand the relevant terms.

The OpenXPKI manual is split into the following sections:

  • The introduction, which you are reading right now. Following this abstract, you will learn more about where to get the software, where to get help. Furthermore, a high-level overview of the system design and some key concepts will be presented.
  • The Quickstart guide lays the emphasis on getting a minimal Certificate Authority (CA) without any bells and whistles running. Reference to further configuration options is provided inline, so that you know where to look if you want to configure more advanced features. Please note that setting up a working CA is a complex task and thus the ‘’quick’’ in “quick start” may be a bit euphemistic.
  • The setup chapter gives a more detailed introduction with brief configuration examples to adjust the system to your needs based on the existing, pre-defined workflows and code.
  • In the operation chapter we describe commands and tools which are required for setting up the non-config items (e.g., the crypto tokens) and to perform daily operation.
  • If you need to extend the system, read the developer section to learn how to use the workflow engine and the connector layer to make the system fit your needs.

Key features

Assuming this is your first contact with OpenXPKI here is a quick summary of what it is and what it is capable of.

OpenXPKI aims to be an enterprise-scale Public Key Infrastructure (PKI) solution, supporting well established infrastructure components like RDBMS and Hardware Security Modules (HSMs). It started as the successor of OpenCA, and builds on the experience gained while developing it as well as on our experience in large public key infrastructures.

  • CA rollover: “Normal” trust center software usually does not account for the installment of a new CA certificate, thus if the CA certificate becomes invalid, a complete re-deployment has to be undertaken. OpenXPKI solves this problem by automatically deciding which CA certificate to use at a certain point in time.
  • Support for multiple so-called PKI realms: Different CA instances can be run in a single installation without any interaction between them, so one machine can be used for different CAs.
  • Private key support both in hardware and software: OpenXPKI has support for professional Hardware Security Modules such as the nCipher nShield or the Safenet Luna CA modules. If such modules are not available, access to a key can be protected by using a threshold secret sharing algorithm.
  • Professional database support: The user can choose from a range of database backends, including commercial ones such as Oracle which are typically used in enterprise scenarios.
  • Many different interfaces to the server: Humans can access the CA server using a web-interface. Embedded devices such as routers can also use the Simple Certificate Enrollment Protocol (SCEP) to talk to the server and apply for certificates - including automatic renewal. If integration into existing systems is required, REST and SOAP interfaces are also available.
  • Workflow Engine: OpenXPKI aims to be extremely customizable by allowing the definition of workflows for any process you can think of in the PKI area. Typical workflows such as editing and approving certificate signing requests, certificate and CRL issuance are already implemented. Implementing your own idea is normally pretty easy by defining a workflow in a YAML configuration file and (maybe) implementing a few lines in Perl.
  • I18N: Localization of the application and interfaces is easily possible.
  • Self-Service application for smartcard/token personalization: A web application which allows a user to easily create and install certificates to a smartcard is available (commercial third-party component required).
  • Template-based certificate details: Contrary to the typical CA system, your users do not need to know about how you would like the subject to look like - you can just ask them for the information they know (for example a hostname and port) and OpenXPKI will create the corresponding subject and subjectAlternativeNames for you. Regular expression support allows you to enforce certificate naming conventions easily.
  • Interchangeble notifation backends We can of course send eMail notifications to customers and operators but if you have a heavy load of certificate requests that need additional communication with the requesters, you can attach a ticket system like [http://www.bestpractical.com/rt/ Request Tracker], which will receive updates on the certificate status.

Quickstart guide

OpenXPKI is an easy-to-deploy and easy-to-use RA/CA software that makes handling of certificates easy but nevertheless you should really have some basic knowledge on what a PKI is. If you just want to see “OpenXPKI in action” for a first impression of the tool, use the public demo at https://demo.openxpki.org.

Support

If you need help, please use the mailing list and do NOT open items in the issue tracker on GitHub. For details and additional support options have a look at http://www.openxpki.org/support.html.

Docker

We also provide a docker image based on the debian packages as well as a docker-compose file, see https://github.com/openxpki/openxpki-docker. Please read the hints in the README if you try this on Windows!

Configuration

The debian package come with a sample configuration, for a production setup we recommend to remove the /etc/openxpki folder created by the package and replace it with a checkout of the community branch of the configuration repository available at https://github.com/openxpki/openxpki-config. Please also have a look at the QUICKSTART.md document which has some more detailed instructions how to setup the system.

Note: The configuration is (usually) backward compatible but most releases introduce new components and new configuration that can not be used with old releases. Make sure your code version is recent enough to run the config! Starting with v3.12, the code checks its compatibility with the config itself via the system.version.depend node. We recommend to keep and maintain this in your config!

Debian Builds

Packages are for Debian 12 (Bookworm) / 64bit (arch amd64). The en_US.utf8 locale must be installed as the translation system will crash otherwise! The packages do NOT work on Ubuntu or 32bit systems. Packages for SLES/CentOS/RHEL/Ubuntu are available via an enterprise subscription

Start with a debian minimal install, we recommend to add “SSH Server” and “Web Server” in the package selection menu, as this will speed up the install later.

To avoid an “untrusted package” warning, you should add our package signing key (you might need to install gpg before):

wget https://packages.openxpki.org/v3/debian/Release.key -O - 2>/dev/null | tee Release.key | gpg -o /usr/share/keyrings/openxpki.pgp --dearmor

The https connection is protected by a Let’s Encrypt certificate but if you want to validate the key on your own, the fingerprint is:

gpg --print-md sha256 Release.key (Updated 2023-06-21)
F88C6BFC 07ACE167 9399CDE5 21BD9148 4F9DA3EB B38E1BFC DA670B1C C96EB501

You can also find the key on the github repository in package/debian/Release.key.

Add the repository to your source list (bookworm):

echo -e "Types: deb\nURIs: https://packages.openxpki.org/v3/bookworm/\nSuites: bookworm\nComponents: release\nSigned-By: /usr/share/keyrings/openxpki.pgp" > /etc/apt/sources.list.d/openxpki.sources
apt update

Please do not disable the installation of “recommend” packages as this will very likely leave you with an unusable system.

As OpenXPKI can run with different RDBMS, the package does not list any of them as dependency. You therefore need to install the required perl bindings and server software yourself:

apt install mariadb-server libdbd-mariadb-perl

We strongly recommend to use a fastcgi module as it speeds up the UI, we recommend mod_fcgid as it is in the official main repository (mod_fastcgi will also work but is only available in the non-free repo):

apt install apache2 libapache2-mod-fcgid

Note, fastcgi module should be enabled explicitly, otherwise, .fcgi file will be treated as plain text (this is usually done by the installer already):

a2enmod fcgid

Now install the OpenXPKI core package, session driver and the translation package:

apt install libopenxpki-perl openxpki-cgi-session-driver openxpki-i18n

use the openxpkiadm command to verify if the system was installed correctly:

openxpkiadm version
Version (core): 3.26.0

Now, create an empty database and assign a database user:

CREATE DATABASE openxpki CHARSET utf8;
CREATE USER 'openxpki'@'localhost' IDENTIFIED BY 'openxpki';
GRANT ALL ON openxpki.* TO 'openxpki'@'localhost';
flush privileges;

…and put the used credentials into /etc/openxpki/config.d/system/database.yaml:

main:
   debug: 0
   type: MariaDB2
   name: openxpki
   host: localhost
   port: 3306
   user: openxpki
   passwd: openxpki

Starting with the v3.8 release we added a MariaDB driver that makes use of MariaDB internal sequences instead of the emulation code and we recommend any new installations to use it! While the MariaDB``drivers uses the old mysql binding the newer ``MariaDB2 uses the modern mariadb perl module which is the recommended driver on modern operating systems.

Please create the empty database schema from the provided schema file. mariadb/mysql and postgresql should work out of the box, the oracle schema is good for testing but needs some extra indices to perform properly.

Example call when debian packages are installed:

cat /usr/share/doc/libopenxpki-perl/examples/schema-mariadb.sql | \
     mysql -u root --password --database  openxpki

If you do not use debian packages, you can get a copy from contrib/sql/ in the config repository https://github.com/openxpki/openxpki-config.

System Setup

Sample / Demo Configuration

The debian package comes with a shell script sampleconfig.sh that does all the work for you (look in /usr/share/doc/libopenxpki-perl/examples/). The script will create a two-stage ca with a root ca certificate and below your issuing ca and certs for SCEP and the internal datasafe.

It will also start the required services, you should be able to log into the system via the webbrowser using the default credentials (see section Testdrive below).

This script provides a quickstart but should never be used for production systems (it has the fixed passphrase root for all keys ;) and no policy/crl, etc config ).

Production Configuration

For a production setup we recommend to remove the /etc/openxpki folder that was installed by the package and use a checkout of the openxpki-config repository at.

You need to create the following keys/certificates yourself if you dont use the sampleconfig script.

  1. Issuing CA certificate (recommend with a Root CA on top of it)
  2. Internal DataVault Certificate
  3. Certificate for the SCEP RA

OpenXPKI supports NIST and Brainpool ECC curves (as supported by openssl) for the CA certificates, as the Datavault certificate is used for data encryption it MUST use an RSA key! You should also remove the democa realm and create a realm with a proper name (see reference/configuration/introduction.html#main-configuration).

Starting with release 3.6 the default config uses the database to store the issuing ca and SCEP tokens - if you upgrade from an older config version check the new settings in systems/crypto.yaml.

As of v3.10 the openxpiadm alias command can be used to manage the keys directly but this requires that the server is started and the directory for the keys exists, the default location is /etc/openxpki/local/keys so we need to create the directory before we proceed:

$ mkdir -p /etc/openxpki/local/keys

We also need to start the server now (there is also an init-script and systemd unit available):

$ openxpkictl start

Starting OpenXPKI...
OpenXPKI Server is running and accepting requests.
DONE.

In the process list, you should see two process running:

14302 ?        S      0:00 openxpki watchdog ( main )
14303 ?        S      0:00 openxpki server ( main )

If this is not the case, check /var/log/openxpki/stderr.log.

Import Root CA

The Root CA is outside the scope of OpenXPKI, we recommend to use clca.

As OpenXPKI needs to be able to build the full chain for any certificate, we need to import the Root CA(s) first:

$ openxpkiadm certificate import --file root.crt
DataVault Token

Create an RSA key with at least 3072 bits, either chose no password or the password configured for the token in your crypto.yaml. Create a self-signed certificate with this key with subject “/CN=DataVault”. You can find a usable sample config file to create an unencrypted key in the contrib folder:

$ openssl req -new -x509 -keyout vault.key -out vault.crt -days 1100 \
    -config /etc/openxpki/contrib/vault.openssl.cnf

Now import the certificate and its key:

$ openxpkiadm certificate import --file vault.crt

Starting import
Successfully imported certificate into database:
  Subject:    CN=Internal DataVault
  Issuer:     CN=Internal DataVault
  Identifier: YsyZ4eCgzHQN607WBIcLTxMjYLI
  Realm:      none

Register it as datasafe token for the democa realm and provide the matching key file to get it loaded into the right place:

$ openxpkiadm alias --realm democa --token datasafe \
    --file vault.crt --key vault.key

Successfully created alias in realm democa:
  Alias     : vault-1
  Identifier: YsyZ4eCgzHQN607WBIcLTxMjYLI
  NotBefore : 2020-07-06 18:54:43
  NotAfter  : 2030-07-09 18:54:43

In case you have multiple realms, you need to run this command for each realm but should omit the key file for any additional realms.

You should check now if your DataVault token is working:

$ openxpkicli  get_token_info --arg alias=vault-1
{
    "key_name" : "/etc/openxpki/local/keys/vault-1.pem",
    "key_secret" : 1,
    "key_store" : "OPENXPKI",
    "key_usable" : 1
}

If you do not see “key_usable”: 1 your token is not working! Check the permissions of the file (and the folders) and if the key is password protected if you have the right secret set in your crypto.yaml!

Issuing CA Token

The creation and management of the Issuing CA keys and certificates themselves is not part of OpenXPKI, you need to have the keys and certificates at hand before you proceed. The keys must either be unprotected or use the secret referenced in the realms crypto.yaml.

The openxpkiadm alias command offers a shortcut to import the certificate, register the token and store the private key. Repeat this step for all issuer tokens in all realms. The system will assign the next available generation number and create all required internal links. In case you choose the filesystem as key storage the command will write the key files to the intended location but requires that the folder exist (/etc/openxpki/local/keys/<realm>):

openxpkiadm alias --realm democa --token certsign \
    --file democa-signer.crt --key democa-signer.pem

If the import went smooth, you should see something like this (ids and times will vary):

$ openxpkiadm alias --realm democa

=== functional token ===
vault (datasafe):
Alias     : vault-1
Identifier: lZILS1l6Km5aIGS6pA7P7azAJic
NotBefore : 2015-01-30 20:44:40
NotAfter  : 2016-01-30 20:44:40

ca-signer (certsign):
Alias     : ca-signer-1
Identifier: Sw_IY7AdoGUp28F_cFEdhbtI9pE
NotBefore : 2015-01-30 20:44:40
NotAfter  : 2018-01-29 20:44:40

=== root ca ===
current root ca:
Alias     : root-1
Identifier: fVrqJAlpotPaisOAsnxa9cglXCc
NotBefore : 2015-01-30 20:44:39
NotAfter  : 2020-01-30 20:44:39

upcoming root ca:
  not set

An easy check to see if the signer token is working is to create a CRL:

$ openxpkicmd  --realm democa crl_issuance
Workflow created (ID: 511), State: SUCCESS

Adding the Webclient

The package installs a default configuration for apache but requires that you configure a tls certificate and setup the configuration for the webui session storage.

TLS Setup

Create a TLS certificate (self-signed or from an external PKI) and copy the key to /etc/openxpki/tls/private/openxpki.pem and the certificate to /etc/openxpki/tls/endentity/openxpki.crt.

The default configuration also offers TLS client authentication. You need to place a copy of your root certificate in /etc/openxpki/tls/chain/ and run c_rehash /etc/openxpki/tls/chain/ to make it available for chain construction in apache. If you don’t want to use client authentication you must remove the SSLCACertificatePath and SSLVerify* options as the webserver will not start if this path is empty.

Session Storage

The default configuration now uses a database backend to store the webui session information. Please review the section [session] and [session_driver] in the file /etc/openxpki/webui/default.conf. It is strongly advised to use a dedicated user here with access only to the frontend_session table for security reasons. You can even put this on a different database as the information is not used by the backend.

If you have a single node setup, you can switch to the filesystem based driver.

Module Setup

Ensure that fcgid is enabled (a2enmod fcgid).

Testdrive

You should now be able to (re)start the apache server:

$ service apache2 restart

Navigate your browser to https://yourhost/openxpki/. If your browser asks you to present a certificate for authentication, skip it. You should now see the main authentication page.

The sample configuration comes with a predefined handler for a local user database and also a set of tests accounts. If you start with the configuration repository, the password for all accounts is openxpki, if you start with the debian package the password is randomized during setup, you will see it on the console during install and can find it in clear text in /etc/openxpki/config.d/realm.tpl/auth/handler.yaml

The usernames are alice and bob (users) and rob, rose and raop (operators). To setup your local user database have a look at the files in the auth directory and the reference/configuration/realm.html#authentication

  1. Login as User (Username: bob, Password: <see above>)
  2. Go to “Request”, select “Request new certificate”
  3. Complete the pages until you get to the status “PENDING” (gray box on the right)
  4. Logout and re-login as RA Operator (Username: raop, Password: <see above> )
  5. Select “Home / My tasks”, there should be a table with one request pending
  6. Select your Request by clicking the line, change the request or use the “approve” button
  7. After some seconds, your first certificate is ready :)
  8. You can download the certificate by clicking on the link in the first row field “certificate”
  9. You can now login with your username and fetch the certificate
Troubleshooting

If you only get the “Open Source Trustcenter” banner without a login prompt, make sure that the fcgi module is properly loaded and available. To see the output of the wrapper script, it might be helpful to use the browsers developer console (F12 or CTRL+F12 on most browsers).

If you get an internal server error, make sure you have the en_US.utf8 locale installed (locale -a | grep en_US)!

For further investigation, check /var/log/openxpki/webui.log and /var/log/apache/error.log.

Enabling the SCEP service

SCEP RA Certificate

Create a certificate to be used as SCEP RA, this is usually a TLS Server certificate from the CA itself or signed by an external CA. Import the certificate and register it as SCEP RA token:

openxpkiadm alias --realm democa --token scep \
    --file scep.crt --key scep.pem

Note: Each realm needs his own SCEP token so you need to run this command any realm that provides an SCEP service. It is possible to use the same SCEP token in multiple realms.

Install SCEP Wrapper

Starting with v3.18, the default configuration uses a pure perl implementation for the SCEP server so there is no need to install any additional tools anymore.

If you run an older configuration or want to stick with LibSCEP for any reason, you have to install the library and perl bindings with:

apt install libcrypt-libscep-perl libscep

The remaining SCEP logic is already included in the core distribution. The package installs a wrapper script into /usr/lib/cgi-bin/ and creates a suitable alias in the apache config redirecting all requests to http://host/scep/<any value> to the wrapper. A default config is placed at /etc/openxpki/scep/default.conf. For a testdrive, there is no need for any configuration, just call http://host/scep/scep.

The system supports getcacert, getcert, getcacaps, getnextca and enroll/renew - the shipped workflow is configured to allow enrollment with password or signer on behalf. The password has to be set in scep.yaml, the default is ‘SecretChallenge’. For signing on behalf, use the UI to create a certificate with the ‘SCEP Client’ profile - there is no password necessary. Advanced configuration is described in the scep workflow section.

The best way for testing the service is the sscep command line tool (available at e.g. https://github.com/certnanny/sscep).

Check if the service is working properly at all:

mkdir tmp
./sscep getca -c tmp/cacert -u http://yourhost/scep/scep

Should show and download a list of the root certificates to the tmp folder.

To test an enrollment:

openssl req -new -keyout tmp/scep-test.key -out tmp/scep-test.csr -newkey rsa:2048 -nodes
./sscep enroll -u http://yourhost/scep/scep \
    -k tmp/scep-test.key -r tmp/scep-test.csr \
    -c tmp/cacert-0 \
    -l tmp/scep-test.crt \
    -t 10 -n 1

Make sure you set the challenge password when prompted (default: ‘SecretChallenge’). On current desktop hardware the issue workflow will take approx. 15 seconds to finish and you should end up with a certificate matching your request in the tmp folder.

Support for Java Keystore

OpenXPKI can assemble server generated keys into java keystores for immediate use with java-based applications like tomcat. This requires a recent version of java keytool installed. On debian, this is provided by the package openjdk-7-jre. Note: You can set the location of the keytool binary in system.crypto.token.javajks, the default is /usr/bin/keytool.

Upgrading OpenXPKI

We try hard to build releases that do not break old installations but sometimes we are forced to make changes that require manual adjustment of existing config or even the database schema.

This page provides a summary of recommended and mandatory changes. Recommended items should be done but the installation will continue to work. Mandatory items MUST be done, as otherwise the system will not behave correctly or even will not start.

For a quick overview of config changes, you should always check the config repository at https://github.com/openxpki/openxpki-config.

Release v3.12

Important: a configuration update is required when upgrading to v3.12

Major rework of the authentication layer - the handlers External and ClientSSO that were also referenced in the default configuration (but of no real use in the default setup) have been removed from the code tree. A similar functionality is available via the new handlers NoAuth and Command. In case you have those handlers as “leftovers” of the default configuration you should just remove them. If you have used them, please adjust the configuration before you upgrade.

Release v3.x

To upgrade from v2 or an earlier v3 installation to v3 please see the Upgrade document in the openxpki-config repository.

In case you have written your own code or used the command line tools please note that the old API was removed, and some output formats have changed! You can find the API documentation as “perldoc” the implementation classes (located in OpenXPKI::Server::API2::Plugin).

Release v2.3

The config parameters for the ClientX509 authentication handler have changed. In case you left the file “handler.yaml” and “stack.yaml” in realm/democa/auth/ unchanged you MUST remove or change the block for the “ClientX509” handler as the new/fixed handler will not work with the old config and OpenXPKI will not start at all!

Release v2.x

Upgrading from v1.x to v2.x requires some manual changes!

You MUST upgrade your database schema:

ALTER TABLE `certificate`
  ADD `revocation_time` int(10) unsigned DEFAULT NULL,
  ADD `invalidity_time` int(10) unsigned DEFAULT NULL,
  ADD `reason_code` varchar(50) DEFAULT NULL,
  ADD `hold_instruction_code` varchar(50) DEFAULT NULL;

UPDATE `crr` crr LEFT JOIN certificate crt USING (identifier)
SET crt.reason_code = crr.reason_code,
    crt.revocation_time = crr.revocation_time,
    crt.invalidity_time = crr.invalidity_time,
    crt.hold_instruction_code = crr.hold_code;

ALTER TABLE `workflow_history`
ADD`workflow_node` varchar(64) DEFAULT NULL;

ALTER TABLE `crl`
ADD `crl_number` decimal(49,0) DEFAULT NULL,
ADD `items` int(10) DEFAULT 0,
ADD KEY `crl_number` (`issuer_identifier`,`crl_number`);

You SHOULD copy over the new default workflows, the old default workflows SHOULD continue working but some of the workflow classes have changed, so in case you made extensions please check the configuration for deltas!

In case you use SCEP please note that the definition of the workflow to use has moved from the “outer” wrapper configuration to the “inner” configuration file inside the realm. You should also switch from the old workflow type “enrollment” to the new “certificate_enroll” which has basically the same functionality but a lot better error handling and extensions. Note that the format of the workflow configuration file was also changed! Check the provided samples for details.

Release v1.19

Warning We changed the internal serialization format which also affects the workflow persistence layer. Workflows or data pool structures that are created or modified will use the new serialization format which cannot be read by older versions! So be aware that a downgrade or parallel operation of new and old release versions is not possible!

Release v1.18

Logging

We removed the internal, hardcoded pattern formatter for the log lines and replaced it with native Log4perl patterns using Log4perl MDC variables to give you more control on what and where to write to. If you do not adjust your configs, you will still get your logs but information on packages, etc. which was hardcoded before is now gone. Check the new sample log.conf for the new format and logging options.

Also note that the timestamps used in the application_log and audittrail table are now written as epoch with microseconds as decimal part.

Sessions

There is a new session handler to get rid of filesystem sessions. The frontend can write back the session information to the backend while the backend can use the database to store the session data. The provided example configuration uses those new handlers as defaults, but the code still uses the old file based sessions if you do not explicitly set the new ones. Note that you must create the sessions table yourself when upgrading:

CREATE TABLE IF NOT EXISTS `session` (
  `session_id` varchar(255) NOT NULL,
  `data` longtext,
  `created` int(10) unsigned NOT NULL,
  `modified` int(10) unsigned NOT NULL,
  `ip_address` varchar(45) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

ALTER TABLE `session`
 ADD PRIMARY KEY (`session_id`), ADD INDEX(`modified`);

If you use backend sessions, please also set the “cookey” secret phrase to encrypt the session cookies in the webui config. Otherwise, a person with access to the server logs can very easily hijack running sessions!

Release v1.13

The default config now uses /var/log/openxpki/ as log directory. It is no problem to leave your log files where there are but you need to fix the permissions on the frontend logs after running the update:

cd /var/openxpki/; chown www-data webui.log scep.log soap.log rpc.log

We will fix this in the Debian update with the next release.

Release v1.11

We put access to workflow log/history/context under access control. If you want your users/operators to have access to those items, you MUST add the new acl items to your workflow definitions:

acl:
  RA Operator:
    creator: any
    fail: 1
    resume: 1
    wakeup: 1
    history: 1
    techlog: 1
    context: 1

If you are using the SOAP revocation interface or want to use the new RPC revocation interface, you MUST add a new field to the inital action.

Add the file config.d/realm/democa/workflow/global/field/interface.yaml to your config tree. In config.d/realm/democa/workflow/def/certificate_revocation_request_v2.yaml add the field “interface” to the list of “input” fields of “create_crr”.

Release v1.10

Please update your database schema:

DROP TABLE IF EXISTS `seq_application_log`;
CREATE TABLE IF NOT EXISTS `seq_application_log` (
  `seq_number` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
  `dummy` int(11) DEFAULT NULL,
  PRIMARY KEY (`seq_number`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `application_log`;
CREATE TABLE IF NOT EXISTS `application_log` (
  `application_log_id` bigint(20) unsigned NOT NULL,
  `logtimestamp` bigint(20) unsigned DEFAULT NULL,
  `workflow_id` decimal(49,0) NOT NULL,
  `priority` int(11) DEFAULT 999,
  `category` varchar(255) NOT NULL,
  `message` longtext,
  PRIMARY KEY (`application_log_id`),
  KEY (`workflow_id`),
  KEY (`workflow_id`,`priority`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

Append “DBI” for the application logger in /etc/openxpki/log.conf:

log4perl.category.openxpki.application = INFO, Logfile, DBI

Setup and Configuration

Overview

Main configuration

The configuration of OpenXPKI consists of two, fundamental different, parts. There is one global system configuration, which holds information about database, filesystem, etc. where the system lives. The second part are the realm configurations, which define the properties of the certificates within the realm. Each pki realm has its own, independant configuration and is isolated from other realm, so you can run instances with different behaviour with one single OpenXPKI server.

We ship the software with a set of YaML files, and we recommend to keep the given layout. The following documentation uses some notations you should know about.

  1. Configuration items are read as a path, which is denoted as a string with the path elements seperated by a dot, e.g. system.database.main.type.
  2. The path is assembled from the directory, the name of the configuration file, the path of the element in the YaML notation. The value from the example above can be found in the directory system, file database.yaml, section main, key type.
  3. All paths except those starting with system or realm refer to the configuration of a particular realm. The root node for building the path is the realm’s directory found at realm/<name of the realm>.

Config versioning

This idea was dropped, configuration is now read freshly from the filesystem at every restart of the daemon.

Activate the new configuration

To activate a new config without a restart, you need to do a reload:

openxpkictl reload

You can also just send a SIGHUP to the main process or restart the dameon.

IMPORTANT

Those parts of the system are preloaded on server init and need a restart to load a new configuration.

  • worflow configuration
  • authentication handlers
  • database settings
  • daemon settings (never change anything below system.server while the dameon is running as you might screw up your system!)

Config Caching and Signing

Instead of reading the config from the filesystem freshly on each startup, there is an option to serialize the config tree into a blob which offers the option to use PKCS7 signatures to verify the configuration.

Transform config tree into blob

Use openxpkiadm buildconfig --config path/to/config.d to generate the blob. The default target is a file config.oxi in the current directory, you specify and alternate location with --target myconfig.blob.

To sign the config blob, you need to add --key and --cert pointing to the PEM encoded key/certificate files. Its good practice to also add the chain certificates to verify the signer up to the root, use --chain to point to a file holding the concatenated PEM block of the issuers to be added.

Enable config bootstrap

You need to create a node named bootstrap on the top level of the configuraion and should remove any other config items. The easiest way to achieve this is to wipe anything from /etc/openxpki/config.d/ and place a file called bootstrap.yaml here:

# this is the only mandatory option
LOCATION: /home/pkiadm/config.oxi

# This is the default and should be left empty unless overridden
# class: OpenXPKI::Config::Loader

# if you want to use signed configs, set ONE of the ca* options
# Path holding the certificates as files (filemame = hash)
# ca_certificate_path: /etc/openxpki/ca/config.certs/
# All certificates in one file
# ca_certificate_file: /etc/openxpki/ca/config.pem

# temp dir, required to create files to perform signature verification
# tmpdir: /tmp

# path to openssl
# openssl: /usr/bin/openssl
Configure signature verification

Signature is validated using openssl smime -verify. If you have placed the chain certificates into the config blob, it is sufficient to have the root certificates here, if not, make sure you have all certificates here to create the full chain. Directory needs to hold the certificates in the well-known openssl hashed filename format, the single file must hold the concatenated PEM blocks.

Note: Currently we do not make any checks on the certificate itself so any certificate from the given roots/chains can be used for signing so it is recommended to setup a dedicated CA for the config signature. We are working on making the signer authorization pattern used in other parts of the system available for config signatures with one of the next releases.

Logging

OpenXPKI uses Log4perl as its primary system log. Logging during startup and in critical situations is done via STDERR.

Global

This document give a brief overview over the system configuration items. You find those settings in the files in the <configfir>/config.d/system directory.

Database

Configure the settings for the database layer. You need at least a configuration for the main database. It is possible to provide a seperate database for logging purposes, just copy the complete block from system.database.main to system.database.logging and adjust as required. If you don’t configure a logging database, the main database is used.

The general configuration block looks like:

main:
    type: supported driver name
    name: name of the database
    host: host
    port: port
    user: database user
    passwd: database credential
    namespace: namespace (to be used with oracle)
    environment:
        db_driver_env_key: value
    driver:
        LongReadLen: 10000000

OpenXPKI supports MariaDB (MySQL), PostgreSQL and Oracle. The namespace parameter is used only by the Oracle driver. Options given to driver are passed to DBI as extra parameters.

Check perldoc OpenXPKI::Server::Database::Driver::<type> for more info on the parameters.

The recommended driver is MariaDB2 which uses the perl MariaDB driver module while MariaDB internally uses the old mysql driver of perl. Depending on the OS and perl version used this might just be an alias but we have also seen very strange issues here.

System

Settings about filesystem, daemon and services to start. Located at system.server

os related stuff

i18n locale settings:

i18n:
    locale_directory: path to the gettext locales on your system
    default_language: supported locale (e.g. en_US.utf8)

Location of the locale files and the default language used. If you set another language than C, make sure you have the correct po-files installed, otherwise OpenXPKI won’t even start! This usually only affects logging and system messages as most of the client related output uses the locale settings from the client session. We recommend using C as default.

daemon settings

Those settings determine the properties of the OpenXPKI daemon openxpkid.:

name:          label for your process list, useful if you are running multiple servers.
user:          Unix user to run as (numeric or name)
group:         Group to run as (numeric or name)

socket_file:   Location of the communication socket.
pid_file:      Location of the pid file.
environment:
    key: value

log4perl:      path to your Log4perl configuration file (the primary system logger).
stderr:        File to redirect stderr to after dettaching from console.
tmpdir:        Location for temporary files, writable by the daemon.

session:
    directory: Directory to store the session information.
    lifetime:  Lifetime of the sessions on the server side.

The socket, pidfile and stderr are created during startup while running as root. The directory must exist, be writeable by root and accessible by the user the daemon runs as. The tmpdir must be writable by the daemon user, it can be a ramfs but can grow large in high volume environments.

system internals

transport:
    Simple: 1

The transport setting is reserved for future use, leave it untouched.

service:
    Default:
        enabled: 1
        timeout: 120

    SCEP:
        enabled: 1

The service block lists all services to be enabled, the key is the name of the service, the enabled key is supported by all services, for all other parameters consult the concrete service documentation (perldoc OpenXPKI::Service::<ServiceName>).

multi-node support

shift: 8
node:
    id: 0

data_exchange:

TODO - this is not used yet

Server Type (Fork vs. PreFork)

The default is Fork which create a new child on every incoming connection, handles the current request and exits. The webui resuses the backend connection as long as the CGI wrapper is running but most of the other clients don’t and there require a new fork on every request.

To reuse existing childs you can set the server type to prefork which forkes of child process on server startup and reuses them for multiple connections. In server.yaml uncomment this block:

type: PreFork
prefork:
  min_servers: 5
  min_spare_servers: 5
  max_servers: 25
  max_spare_servers: 10

The option is optional, if not provided the defaults of the Net::Server module are used.

Watchdog

The openxpkid daemon forks a watchdog process to take care of background processes. It is initialised with default settings, but you can provide your own values by setting them at system.watchdog.

# How to deal with exceptions
max_exception_threshhold: 10
interval_sleep_exception: 60
max_tries_hanging_workflows:  3

# Control the wait intervals
interval_wait_initial: 60
interval_loop_idle: 5
interval_loop_run: 1

# You should not change this unless you know what you are doing
max_instance_count: 1
disabled: 0

Please see perldoc OpenXPKI::Server::Watchdog for details.

Crypto layer (global)

Define several parameters for the basic crypto tools.

api settings

You should not need to touch this unless you are developing your own crypto classes.

tokenapi:
    certsign:      OpenXPKI::Crypto::Backend::API
    datasafe:      OpenXPKI::Crypto::Backend::API
    scep:          OpenXPKI::Crypto::Tool::SCEP::API

The setting denotes the name of the perl module used as backend class when using a token of the given class. Default tokens are certsign, is used for all ca operations, and datasafe, used to internally´ encrypt data. Any tokens that are not defined here, use OpenXPKI::Crypto::Backend::API by default. If you run a scep server, you must add the line for the scep module, as it does not work with the default.

configuration of the default tokens

token:
    default:
        backend: OpenXPKI::Crypto::Backend::OpenSSL
        api:     OpenXPKI::Crypto::Backend::API
        engine:  OpenSSL
        key_store: OPENXPKI

        # OpenSSL binary location
        shell: /usr/bin/openssl

        # OpenSSL binary call gets wrapped with this command
        wrapper: ''

        # random file to use for OpenSSL
        randfile: /var/openxpki/rand

    pkcs7:
        backend: OpenXPKI::Crypto::Tool::PKCS7
        api: OpenXPKI::Crypto::Tool::PKCS7::API

    javaks:
        backend: OpenXPKI::Crypto::Tool::CreateJavaKeystore
        api: OpenXPKI::Crypto::Tool::CreateJavaKeystore::API

If you have non-standard file locations, you might want to change the OpenSSL relevant settings here, the wrapper allows you to provide the name of a wrapper command which is commonly necessary if you use hardware security modules or other special OpenSSL eninges for your crypto operations. See the section about using HSMs for more details.

Developer note: See OpenXPKI::Crypto::TokenManager::get_system_token

PKI Realms

The detailed settings of each realm are given in the specific realm configuration. To use a realm you need to specify and enable it at system.realms.

democa:
    label: This is just a verbose label for your CA

You should use only 7bit word characters and no spaces as name for the realm.

Realm

In order to create a new realm you must create a new directory below config.d/realm with the internal name of the realm. While it is possible to just create a symlink or copy from the realm.tpl directory we recommend to read the instructions in the Quickstart document as this approach gives you the best options for later upgrades of the configuration.

When finished add a new section in the file system/realms.yaml where the new section key is identical to the new realm directory name used for the realm directory. Change the new realm section entries to match the desired values for the new realm.

Please note that you might need to perform additional steps based on the overall configuration options such as creating templates, static content or mapping items. Those should be outlined in the configurations setup document.

The realm configuration consists of five major parts:

Authentication
Configure which authentication mechanisms to use for the realm. If you use internal authentication methods, this also holds your user databases.
Crypto layer
Define name and path of your keyfiles and settings for the crypto tokens used.
Profiles
Anything related to your certificate and crl profiles
Publishing
How and where to publish certificates and crls
Workflow
Configuration data of the internal workflow engine.

Authentication

Authentication is done via authentication handlers, multiple handlers are combined to an authentication stack.

Stack

The realm’s authentication combines the configured authentication handlers to offer different authentication stacks. On the login page, the entries of the stack are shown and a user can choose between them. The stacks are configured in the file located in <realm-basedir>/auth/stack.yaml. If, for example, one would like to enable only anonymous logins and password based logins, this file’s contents could be as follows:

Anonymous:
    label: Guest Login
    handler: Anonymous
    type: anon

User:
    label: User Login
    type: passwd
    handler:
      - Operator Password
      - User Password

In this configuration, the realm offers two login stacks, namely Anonymous and User. The stack Anonymous uses the Handler Anonymous and the logins using the stack User may be performed by both handlers Operator Password and User Password. Therefore, when selecting this variant, both logins with credentials configured for Operator Password and for User Password are supported. You can define any number of stacks and reuse the same handlers inside. You must define at least one stack.

Advanced Usage

Use the TLS Client Certificate from the HTTPS connection:

Certificate:
    handler: Certificate
    type: x509

Use the REMOTE_USER field from basic auth, optionally pass additonal ENV keys from advanced auth modules:

BasicAuth:
    handler: NoAuth
    type: client
    envkeys:
        email: AUTH_PROVIDER_email_field
Handler

A handler consists of a perl module, that provides the authentication mechanism. The name of the handler is used to be referenced in the stack definition, mandatory entries of all handlers is type. All handlers are defined below OpenXPKI::Server::Authentication, where type is equal to the name of the module.

Here is a list of some handlers and their configuration sets, more can be found in the sample configuration. Extensive documentation can be found in the perldoc for the classes.

Anonymous user

If you just need an anonymous connection, you can use the Anonymous handler.

Anonymous:
    type: Anonymous
    # the verbose name is shown in the UI
    name: Guest User

System:
    type: Anonymous
    role: System

If no role is provided, you get the anonymous role. Do never set any other role than system, unless you exactly know what you are doing!

x509 based authentication

X509 uses the SSL client authentication feature of apache/mod_ssl. It passes the signer certificate to a validation function that cryptographically checks the chain and tests the chain against a list of trusted anchors.

The configuration is the same for both handlers (apart from the class name):

Certificate:
    type: ClientX509
    role: User
    trust_anchor:
        realm: userca

Please check perldoc OpenXPKI::Server::Authentication::X509 for details.

Password database handler

The password database handler allows to specify user/password/role pairs directly inside the configuration.

Password:
    type: Password
    description: I18N_OPENXPKI_CONFIG_AUTH_HANDLER_DESCRIPTION_PASSWORD
    user:
        John Doe:
            digest: "{SSHA}TZXM/aqflDDQAmSWVxSDVWnH+NhxNU5w"
            role: User
        root:
            digest: "{SSHA}+u48F1BajP3ycfY/azvTBqprsStuUnhM"
            role: CA Operator
        raop:
            digest: "{SSHA}ejZpY22dFwjVI48z14y2jYuToPRjOXRP"
            role: RA Operator

The passwords are hashed, the used hash algorithm is given as prefix inside the curly brackets. You should use only SSHA which is “salted sha1”. For compatibility we support plain sha (sha1), md5, smd5 (salted md5) and crypt. You can created the salted passwords using the openxpkiadm CLI tool (openxpkiadm hashpwd). Alternatively, for batch processing, a salted sha1 password could be generated using openssl:

salt="$(openssl rand -base64 3)"
password="secretpassword"
echo -n $(echo -n "$password$salt" | openssl sha1 -binary)$salt | openssl enc -base64

Note: As of v3.10 we also directly support the format of the openssl passwd command starting with the Dollar sign.

If you plan to use static passwords for a larger amount of users, you should consider to use a connector instead:

Password:
    type: Password
    user@: connector:auth.connector.userdb

Define the user database file inside auth.connector.yaml:

userdb:
    class: Connector::Proxy::YAML
    LOCATION: /home/pkiadm/democa-userdb.yaml

The user file has the same structure as the user section above, the user names are the on the top level:

root:
    digest: "{SSHA}+u48F1BajP3ycfY/azvTBqprsStuUnhM"
    role: CA Operator
raop:
    digest: "{SSHA}ejZpY22dFwjVI48z14y2jYuToPRjOXRP"
    role: RA Operator

You can share a user database file within realms.

Authentication connectors

There is a family of authentication connectors. The main difference against other connector is, that the password is passed as a parameter and is not part of the path. Check for connectors starting with Connector::Builtin::Authentication. The connector only validates the password, therefore the role must be set in the configuration (same for all users handled by this item):

Password Connector:
    type: Connector
    role: User
    source@: connector:auth.connector.localuser

An example config to authenticate RA Operators against ActiveDirectory using their company mail address and windows password including check of a group membership (this is just the authentication, set the role in the handler config):

raop-ad:
    class: Connector::Builtin::Authentication::LDAP
    LOCATION: ldap://ad.company.com
    base: dc=company,dc=loc
    binddn: cn=binduser
    password: secret
    filter: "(&(mail=[% LOGIN %])(memberOf=CN=RA Operator,OU=SecurityGroups,DC=company,DC=loc))"

External authentication

If you have a proxy or sso system in front of your OpenXPKI server that authenticates your users, the external handler can be used to set the user information:

ExternalAuth:
    type: NoAuth
    role: User

Crypto layer

group assignment

You must provide a list of token group names at crypto.type to tell the system which token group it should use for a certain task. The keys are the same as used in system.crypto.tokenapi (see Crypto layer (global)). See TODO for a detailed view how the token assignment works.

type:
  certsign: ca-certsign
  datasafe: vault
  scep: scep
token setup

Any token used within OpenXPKI needs a corresponding entry in the realm’s token configuration at crypto.token. The name of the token is the alias name you used while registering the correspondig certificate.

token:
  democa-certsign:
    backend: OpenXPKI::Crypto::Backend::OpenSSL

    key: /etc/openxpki/local/keys/democa/ca-certsign-1.pem

    # possible values are OpenSSL, nCipher, LunaCA
    engine:         OpenSSL
    engine_section: ''
    engine_usage:   ''
    key_store:      OPENXPKI

    # OpenSSL binary location
    shell: /usr/bin/openssl

    # OpenSSL binary call gets wrapped with this command
    wrapper: ''

    # random file to use for OpenSSL
    randfile: /var/openxpki/rand

    # Secret group
    secret: default

The most important setting here is key which must be the absolute filesystem path to the keyfile. The key must be in PEM format and is protected by a password. The password is taken from the secret group mentioned by secret. See TODO for the meaning of the other options.

using inheritance

Usually the tokens in a system share a lot of properties. To simplify the configuration, it is possible to use inheritance in the configuration:

token:
    default:
        backend: OpenXPKI::Crypto::Backend::OpenSSL
        ......
        secret: default

    server-ca-1:
        inherit: default
        key: /etc/openxpki/local/keys/democa/ca-certsign-1.pem
        secret: gen1pass

    server-ca-2:
        inherit: default
        key: /etc/openxpki/local/keys/democa/ca-certsign-2.pem

Inheritance can daisy chain profiles. Note that inheritance works top-down and each step replaces all values that have not been defined earlier but are defined on the current level. Therefore you should not use undef values but the empty string to declare an empty setting.

You can use template toolkit to autoconfigure the key property, this way you can roll over your key without modifying your configuration.

The example above will then look like:

token:
    default:
        backend: OpenXPKI::Crypto::Backend::OpenSSL
        key: /etc/openxpki/local/keys/democa/[% ALIAS %].pem
        ......
        secret: default

    server-ca-1:
        inherit: default
        secret: gen1key

    server-ca-2:
        inherit: default

If you need to name your keys according to a custom scheme, you also have GROUP (ca-signer) and GENERATION (1) available for substitution. The certificate identifier is also available via IDENTIFIER.

token in datapool

Instead of having the tokens key files on the filesystem it is possible to store them in the datapool. Please be aware of the security implications of putting your CAs PRIVATE KEYS into the datapool which is readable by anybody with access to the database or the openxpki socket!

You must set the attribute key_store to DATAPOOL and provide the name of the used datapool key using the key attribute:

scep:
    inherit: default
    key_store: DATAPOOL
    key: "[% ALIAS %]"

This will read the SCEP key from the datapool, the used namespace is sys.crypto.keys. You must import the key yourself, e.g. from the CLI:

openxpkicli set_data_pool_entry --arg namespace=sys.crypto.keys \
    --arg key=scep-1 \
    --arg encrypt=1 \
    --filearg value=file_with_key.pem

Using the datapool encryption hides the value of the key from database admins but still exposes it in clear text to anybody with access to the command line tool! It should be obvious that you can not store the data-vault token this way as it is needed to decrypt the datapool items!

Starting with v3.8 the openxpkiadm alias command can handle key imports internally, you can load the certificate and key in one step:

openxpkiadm alias --realm democa --token scep \
    --file democa-scep.crt --key democa-scep.pem

HSM via PKCS#11

Tokens may be maintained by HSMs as well. For HSMs a standardized interface called PKCS#11 is defined. OpenSSL supports this interface as well through its pkcs11 engine. This OpenSSL engine is supplied by the OpenSC and has to be configured in OpenXPKI.

To use PKCS#11 token in OpenXPKI the following settings has to be made:

  • The engine has to be set to PKCS11. This causes OpenXPKI to use OpenSSL’s PKCS#11 engine.
  • The key has to correspond to the key’s identification of the HSM. For example when the YubiHSM2 is used, the string slot_0-label_issuer_key would correspond to a stored key with the label issuer_key.
  • As engine_section one can define how OpenSSL accesses the HSM. OpenXPKI always generates OpenSSL configurations on the fly when needed and if this token is accessed, the contents of OpenSSL’s [engine_section] are pasted in this configuration file. To define which passphrase is used to unlock the HSM, the configuration parameter PIN should be set as shown in the example. OpenXPKI ensures to replace any occurrence of the string __PIN__ with the corresponding secret.
  • The value of engine_usage defines when the engine should be used. Often ALWAYS is the preferred setting.

To use PKCS#11 tokens in OpenXPKI, the backend of the token has to be set to PKCS11.:

token:
  signer:
    backend: OpenXPKI::Crypto::Backend::OpenSSL
    key: "slot_0-label_issuer_key"
    engine: PKCS11
    engine_section: |
      engine_id              = pkcs11
      dynamic_path           = /usr/lib/engines/engine_pkcs11.so
      MODULE_PATH            = /usr/lib/x86_64-linux-gnu/pkcs11/yubihsm_pkcs11.so
      PIN                    = __PIN__
      init                   = 0
    engine_usage: 'ALWAYS'
    key_store: ENGINE
    shell: /usr/bin/openssl
    randfile: /var/openxpki/rand
    wrapper: ''
    secret: signer

The linked secret is only used to get access to the HSM. The secret used to unlock the HSM can be configured normally. For the YubiHSM2 for example a secret group that uses the authentication key 0x0001 with the password password would be the following:

secret:
  signer:
    label: YubiHSM password
    method: literal
    value: 0001password
    cache: daemon

Note: To be able to use the YubiHSM2 with OpenSSL, two environment variables has to be set (YUBIHSM_PKCS11_CONF and YUBIHSM_PKCS11_MODULE). If those environment variables are set when the server is started, the OpenXPKI process inherits these values.

Secret Groups

A secret group maintains the password cache for your keys and PINs. You need to setup at least one secret group for each realm. The most common version is the plain password:

secret:
  default:
    label: One Piece Password
    method: plain
    cache: daemon

This tells the OpenXPKI daemon to ask for the default only once and then store it “forever”. If you want to have the secret cleared at the end of the session, set cache: session.

To increase the security of your key material, you can configure secret splitting by dividing the PIN entry into n components that are simply concatenated.

secret:
  ngkey:
    label: Split secret Password
    method: plain
    total_shares: 3
    cache: daemon

If you have a good reason to put your password into the configuration, use the literal type:

secret:
  insecure:
    label: A useless Password
    method: literal
    value: my_not_so_secret_password
    cache: daemon

You can also use the secret groups for other purposes, in this case you need to add “export: 1” to the group. This allows you to use the get_secret method of the TokenManager (OpenXPKI::Crypto::TokenManager) to retrieve the plain value of the secret.

Profiles

certificates

There is a TODO:link seperate section about certificate profile configuration.

certificate revocation list

A basic setup must provide at least a minimum profile for crl generation at crl.default:

digest: sha1
validity:
    nextupdate: +000014
    renewal: +000003

The nextupdate value gives the validity of the created crl (14 days). The renewal value tells OpenXPKI how long before the expiry date of the current crl the system is allowed to create a new one. If you set this to a value larger than nextupdate, a new crl is created every time you trigger a new crl creation workflow. Note: If a certificate becomes revoked, the renewal interval is not checked.

crl at “end of life”

Once your ca certificate exceeds its validity, you are no longer able to create new crls (at least if you are using the shell modell). OpenXPKI allows you to define a different validity for the last crl, which is taken if the next calculated renewal time will exceed the validity of the ca certificate:

validity:
    nextupdate: +000014
    renewal: +000003
    lastcrl: 20301231235900

crl extensions

The following code shows the full set of supported extensions, you can skip what you do not need:

extensions:
    authority_info_access:
        critical: 0
        ca_issuers: http://myca.mycompany.com/[% CAALIAS.ALIAS %]/cacert.pem
        ocsp:
        - http://ocsp1.mycompany.com/
        - http://ocsp2.mycompany.com/

    authority_key_identifier:
        critical: 0
        keyid:  1
        issuer: 1


    issuer_alt_name:
        critical: 0
        # If the issuer has no subject alternative name, copying returns
        # an empty extension, which is problematic with both RSA SecurId
        # tokens and Cisco devices!
        copy: 0

There are two specialities in handling the ca_issuers and ocsp entries in the authority_info_access section:

  1. You can pass either a list or a single scalar to each item.
  2. For each item, template expansion based on the signing ca certificate is available. See TODO:link for details.

The CAALIAS hash also offers the components of the alias in GENERATION and GROUP.

Publishing

Publishing of certificates and crl is done via connectors (TODO:link). The default workflows look for targets at publishing.entity and publishing.crl. Each target can contain a list of key-value pairs where the value points to a valid connector item while the keys are used for internal logging:

entity:
    int-repo@: connector:publishing.connectors.ldap
    ext-repo@: connector:publishing.connectors.ldap-ext

crl:
    crl@: connector:publishing.connectors.cdp

certificate publishing

The OpenXPKI packages ship with a sample configuration for LDAP publication but you might include any other connector. The publication workflow appends the common name of the certificate to the connector path and passes a hash containing the subject (subject) and the DER (der) and PEM (pem) encoded certificate.

The configuration block looks like this:

connectors:
    ldap-ext:
        class: Connector::Proxy::Net::LDAP::Single
        LOCATION: ldap://localhost:389
        base: ou=people,dc=mycompany,dc=com
        filter: (|(mail=[% ARG %]) (objectCategory=person))
        binddn: cn=admin,dc=mycompany,dc=com
        password: secret
        attrmap:
            der: usercertificate;binary

        create:
            basedn: ou=people,dc=mycompany,dc=com
            rdnkey: cn

        schema:
            cn:
                objectclass: inetOrgPerson
                values:
                    sn: copy:self
                    ou: IT Department

Let’s explain the parts.

class: Connector::Proxy::Net::LDAP::Single
LOCATION: ldap://localhost:389
base: ou=people,dc=mycompany,dc=com
filter: (|(mail=[% ARG %]) (objectCategory=person))
binddn: cn=admin,dc=mycompany,dc=com
password: secret

Use the Connector::Proxy::Net::LDAP::Single package and use cn=admin,dc=mycompany,dc=com and secret to connect with the ldap server at ldap://localhost:389 using ou=people,dc=mycompany,dc=com as the basedn. Look for an entry of class person where the mailadress is equal to the common name of the certificate.

attrmap:
    der: usercertificate;binary

Publish the content of the internal key der to the ldap attribute usercertificate;binary.

create:
    basedn: ou=people,dc=mycompany,dc=com
    rdnkey: cn

This enables the auto-creation of non-existing nodes. The dn of the new node is create from the basedn and the new component of class “cn” set to the path-item which was passed to the connector (in our example the mailadress). You also need to pass the structural information for the node to create.

schema:
    cn:
        objectclass: inetOrgPerson
        values:
            sn: copy:self
            ou: IT Department

crl publishing

The crl publication workflow appends the common name of the ca certificate to the connector path and passes a hash containing the subject (subject), the components of the parsed subject as hash (subject_hash) and the DER (der) and PEM (pem) encoded crl.

The default configuration comes with a text-file publisher for the crl:

cdp:
    class: Connector::Builtin::File::Path
    LOCATION: /var/www/openxpki/myrealm/crls/
    file: "[% ARGS %].crl"
    content: "[% pem %]"

If the dn of your current ca certificate is like “cn=My CA1,ou=ca,o=My Company,c=us”, this connector writes the PEM encoded crl to the file /var/www/openxpki/myrealm/crls/My CA1.crl

Notification

Notifications are triggered from within a workflow. The workflow just calls the notification layer with the name of the message which should be send, which can result in no message or multiple messages on different communication channels.

The configuration is done per realm at notification. Supported connectors are Mail via SMTP (plain and html) and RT Request Tracker (using the RT::Client::REST module from CPAN). You can use an arbitrary number of backends, where each one has its own configuration at notification.mybackend.

Most parts of the messages are customized using the Template Toolkit. The list of available variables is given at the end of this section.

Sending mails using SMTP

You first need to configure the SMTP backend parameters:

backend:
    class: OpenXPKI::Server::Notification::SMTP
    host: localhost
    port: 25
    starttls: 0
    username: smtpuser
    password: smtpsecret
    debug: 0
    use_html: 0

Class is the only mandatory parameter, the default is localhost:25 without authentication. Debug enables the Debug option from Net::SMTP writing to the stderr.log which can help you to test/debug mail delivery. To use html formatted mails, you need to install MIME::Lite and set use_html: 1. The handler will fall back to plain text if MIME::Lite can not be loaded.

The mail templates are read from disk from, you need to set a base directory:

template:
    dir:   /home/pkiadm/democa/email/

Below is the complete message configuration as shipped with the default issuance workflow:

default:
    from: no-reply@mycompany.com
    reply: helpdesk@mycompany.com
    to: "[% cert_info.requestor_email %]"
    cc: helpdesk@mycompany.com

message:
    csr_created:   # The message Id as referenced in the activity
        user:   # The internal handle for this thread
            template: csr_created_user
            subject: CSR for [% cert_subject %]
            prefix: PKI-Ticket [% meta_wf_id %]
            images:
                banner: head.png
                footer: foot.png

        raop:      # Another internal handle for a second thread
            template: csr_created_raop  # Suffix .txt is always added!
            to: reg-office@mycompany.com
            cc: ''
            reply: "[% cert_info.requestor_email %]"
            subject: CSR for [% cert_subject %]

    csr_rejected:
        user:
            template: csr_rejected
            subject: CSR rejected for [% cert_subject %]

    cert_issued:
        user:
            template: cert_issued
            subject: certificate issued for [% cert_subject %]

The default section is not necessary but useful to keep your config short and readable. These options are merged with the local ones, so any local variable is possible and you can overwrite any default at the local configuration (to clear a setting use an empty string, the images hash is NOT merged recursively).

the idea of threads

You might have recognized that there are two blocks below messages.csr_created. Those are so called threads, which combine messages sent at different times to share some common settings. With the first message of a thread the values given for to, cc and prefix are persisted so you can ensure that all messages that belong to a certain thread go to the same receipients using the same subject prefix. Note, that settings to those options in later messages are ignored!

receipient information

The primary receipient and a from address are mandatory:

  • to: The primary receipient, single value, parsed using TT
  • from: single value, NOT parsed

Additional receipients and a seperate Reply-To header are optional:

  • cc: comma seperated list, parsed using TT
  • reply: single value, NOT parsed

All values need to be rfc822 compliant full addresses.

composing the subject

The subject is parsed using TT. If you have specified a prefix, it is automatically prepended.

composing the message body

The body of a message is read from the filename specified by template, where the suffix ‘.txt’ is always apppended. So the full path for the message at messages.csr_created.user is /home/pkiadm/democa/email/csr_created_user.txt.

html messages

If you use the html backend, the template for the html part is read from csr_created_user.html. It is allowed to provide either a text or a html template, if both files are found you will get a multipart message with both message parts set. Make sure that the content is the same to avoid funny issues ;)

It is possible to use inline images by listing the image files with the images key as key/value list. The key is the internal identifier, to be used in the html template, the value is the name of the image file on disk.

With a config of:

user:
    template: csr_created_user
    ....
    images:
        banner: head.png
        footer: foot.png

You need to reference the image in the html template like this:

<body>
    <img src="cid:banner" title="My Company Logo Banner" />
    .....
    <img src="cid:footer" title="My Company Logo Footer" />
</body>

The images are pulled from the folder images below the template directory, e.g. /home/pkiadm/democa/email/images/head.png. The files must end on gif/png/jpg as the suffix is used to detect the correct image type.

To test your notification config, you can trigger a test message via the command line interface:

openxpkicli send_notification --arg message=testmail --param notify_to=me@company.org
RT Request Tracker

The RT handler can open, modify and close tickets in a remote RT system using the REST interface. You need to install RT::Client::REST from CPAN and setup the connection:

backend:
    class: OpenXPKI::Server::Notification::RT
    server: http://rt.mycompany.com/
    username: pkiuser
    password: secret
    timeout: 30

The timeout value is optional with a default of 30 seconds.

As the SMTP backend, it uses templates on disk to build the ticket contents, so we also need to set the template directory:

template:
    dir:   /home/pkiadm/democa/rt/

You can share the templates for SMTP and RT handler and reuse most parts of your configuration, but note that the syntax is slightly different from SMTP. Here is the complete message configuration as shipped with the default issuance workflow:

message:
    csr_created:  # The message Id as referenced in the activity
        main:     # The internal handle for this ticket
            - action: open
              queue: PKI
              owner: pki-team
              subject: New CSR for [% cert_subject %]
              to: "[% cert_info.requestor_email %]"
              template: csr_created
              priority: 1

            - action: comment
              template: csr_created_comment
              status: open

    csr_approved:
        main:
            - action: update
              status: working

    csr_rejected:
        main:
            - action: correspond
              template: csr_rejected
              priority: 10

    cert_issued:
        main:
            - action: comment
              template: cert_issued_internals

            - action: correspond
              template: cert_issued
              status: resolved

The RT handler also makes use of threads, where each thread is equal to one ticket in the RT system. The example uses only one thread = one ticket. Each message can have multiple threads and each thread consists of at least one action.

Create a new ticket

You should make sure that a ticket is created before you work with it! The minimum information required to open a ticket is:

action: open
queue: PKI
owner: pki-team
subject: New CSR for [% cert_subject %]
to: "[% cert_info.requestor_email %]"

The to field must be an email address, which is used to fill the requestor field in RT.

Additional fields are:

  • cc: comma sep. list of email addresses to be assigned to the ticket, parsed with TT
  • template: filename for a TT template, used as inital text for the ticket (.txt suffix is added)
  • priority: priority level, usually a numeric value
  • status: ticket status, usually one of “new”, “open”, “resolved”, “stalled”, “rejected”, and “deleted”.

comment or correspond to a ticket

The maximum configuration is:

action:   comment  # or "correspond"
status:   open     # optional
priority: 5        # optional
template: csr_created_comment  # .txt is added

For comment the result of the parsed template is added to the ticket history.

For correspond the result is also mailed to the ticket receipients (this is a feature of RT, we dont send any mails).

Note: If the template parser returns an empty string, no operation is done on the ticket.

update status/priority without text

The update action allows you to set status/priority without creating a text entry in the history:

action: update
status: stalled
priority: 0

You can call update with either status or priority or both.

setting custom fields

You can set custom field values using the update action. Any key/value pair in the block (except the ones above) is considered to be a custom field. The values are parsed using TT:

action: update
priority: 3
custom-field1: My custom value
custom-field2: My other custom value

Note: This feature is untested!

closing a ticket

You can close a ticket with the above commands by setting the status-flag. For convenience there is a shortcut, setting the status to “resolved”:

action: close
Template Variables

The notification handler injects those values into the template parser on any invocation.

realm info

  • meta_pki_realm (key of the current realm)
  • meta_label (verbose realm name as defined at system.realms.$realm.label)
  • meta_baseurl (baseurl as defined at system.realms.$realm.baseurl)

request related context values (scalars)

  • csr_serial
  • cert_subject
  • cert_identifier
  • cert_profile

request related context values (hashes)

  • cert_subject_parts
  • cert_subject_alt_name
  • cert_info
  • approvals

misc

  • creator
  • requestor (real name of the requestor, if available assembled from cert_info.requestor_gname + requestor_name, otherwise the word “unknown”)

Certificate Info Plugin

The default install also provides a plugin to get detailed informations on a certificate:

[% USE Certificate %]

Serial: [% Certificate.serial(cert_identifier) %]
Hex Serial: [% Certificate.serial_hex(cert_identifier) %]
CSR: [% Certificate.csr_serial(cert_identifier) %]
Issuer: [% Certificate.issuer(cert_identifier) %]
Status: [% Certificate.status(cert_identifier) %]

Body-Subject: [% Certificate.body(cert_identifier, 'Subject') %]

The body method will return any field of the body structure offered by the get_cert api method. Fore further info check the modules documentation (OpenXPKI::Template::Plugin::Certificate).

Workflow

The definition of the workflows is still in the older xml format, already used in older OpenXPKI releases but its management is included into the connector now. The XML files are located in the folder named _workflow (note the underscore!) in the top level direcotry of the realm. If you are upgrading from an older installation, you can just move your old workflow*.xml files here and add an outer “openxpki” tag to the workflow.xml file.

Workflow Definition

Each workflow is represented by a file or directory structure below workflow.def.<name> inside the realm configuration. The name of the file is equal to the internal name of the workflow. Each such file must have the following structure, not all attributes are mandatory or useful in all situations:

head:
    label: The verbose name of the workflow, shown on the UI
    description: The verbose description of the workflow, shown on the UI
    prefix: internal short name, used to prefix the actions, must be unique
            Must not contain any other characters than [a-z0-9]

state:
    name_of_state:  (used as literal name in the engine)
        autorun: 0/1
        autofail: 0/1
        label: visible name
        description: the text for the page head
        action:
          - name_of_action > state_on_success ? condition_name
          - name_of_other_action > other_state_on_success !condition_name
        hint:
            name_of_action: A verbose text shown aside of the button
            name_of_other_action: A verbose text shown aside of the button
        button:
            name_of_action:
                label: Label to put on the button (default is label from action)
                format: layout of the button = assigned stylesheet
                # add a confirmation popup when button is pressed
                confirm:
                    label: Headline of the popup dialog
                    description: Text inside the popup
                    confirm: label of the proceed button
                    cancel: label of the abort button

action:
    name_of_action: (as used above)
        label: Verbose name, shown as label on the button
        tooltip: Hint to show as tooltip on the button
        description: Verbose description, show on UI page
        class: Name of the implementation class
        button: Label for the submit button (default is "continue")
        abort: state to jump to on abort (UI button, optional) # not implemented yet
        resume: state to jump to on resume (after exception, optional) # not implemented yet
        validator:
          - name_of_validator (defined below)
        input:
          - name_of_field (defined below)
          - name_of_other_field
        param:
            key: value - passed as params to the action class

field:
    field_name: (as used above)
        name:        key used in context
        label:       The fields label
        placeholder: Hint text shown in empty form elements
        tooltip:     Text for "tooltip help"
        type:        Type of form element (default is input)
        required:    0|1
        default:     default value
        api_type:    Shortcut syntax to specify an OpenAPI type
        api_label:   Label to use in OpenAPI specification
        more_key:    other_value  (depends on form type)

validator:
    class: OpenXPKI::Server::Workflow::Validator::CertIdentifierExists
    param:
        emptyok: 1
    arg:
      - $cert_identifier

Note: All entity names must contain only letters (lower ascii), digits and the underscore.

Below is a simple, but working workflow config (no conditions, no validators, the global action is defined outside this file):

head:
    label: I am a Test
    description: This is a Workflow for Testing
    prefix: test

state:
    INITIAL:
        label: initial state
        description: This is where everything starts
        action: run_test1 > PENDING

    PENDING:
        label: pending state
        description: We hold here for a while
        action: global_run_test2 > SUCCESS

    SUCCESS:
        label: finals state
        description: It's done - really!
        status:
            level: success
            message: This is shown as green status bar on top of the page

action:
    run_test1:
    label: The first Action
    description: I am first!
    class: Workflow::Action::Null
    input: comment
    param:
        message: "Hi, I am a log message"

field:
    comment: (as used above)
        name: comment
        label: Your Comment
        placeholder: Please enter a comment here
        tooltip: Tell us what you think about it!
        type: textarea
        required: 1
        default: ''

Workflow Head

States

The action attribute is a list (or scalar) holding the action name and the follow up state. Put the name of the action and the expected state on success, seperated by the > sign (is greater than).

Actions

t.b.d.

Fields

SELECT field with options
type: select
option:
    item:
      - unspecified
      - keyCompromise
      - CACompromise
      - affiliationChanged
      - superseded
      - cessationOfOperation
    label: I18N_OPENXPKI_UI_WORKFLOW_FIELD_REASON_CODE_OPTION

If the label tag is given (below option!) the values in the drop down are i18n strings made from label + uppercase(key), e.g I18N_OPENXPKI_UI_WORKFLOW_FIELD_REASON_CODE_OPTION_UNSPECIFIED.

OpenAPI specific field parameters
api_type: Array[Str]
api_label: List of surnames

To be able to generate the OpenAPI spec the data types of all relevant input/output parameters must be defined. The most precise way to do this is to specify api_type in a field definition.

If api_type is not given then OpenXPKI tries to deduce the correct OpenAPI type from the field parameters format and type (and from the field name in some rare cases). See Perl class OpenXPKI::Server::API2::Plugin::Workflow::get_openapi_typespec for technical details.

api_type

api_type accepts a custom shortcut syntax to define OpenAPI data types. The syntax is close to the syntax used in Moose types. All type names are case insensitive.

Supported types

  • String alias Str

  • Integer alias Int

  • Numeric alias Num

  • Boolean alias Bool

  • Array alias ArrayRef

    The type of array items may be specified in square brackets:

    Array[ Str ]
    Array[ Str | Int ]
    
  • Object alias Obj, Hash, HashRef

    The object properties (i.e. hash items) may be specified in square brackets:

    Object[ age: Integer, name: String ]
    

Type parameters/modifiers

Modifiers may be passed in brackets. Please note that those modifiers are case sensitive as they are used as-is in the OpenAPI spec.

String(format:password)
Integer(minimum: 1)

Examples

Some more complex examples of nested types:

Array[ Object[ comment:Str, names:Array[Str] ] ]
HashRef[ size:Integer(minimum:5), data:Array, positions:Array[ Integer | Numeric ] ]

Please note

  • types are case insensitive
  • you can insert spaces wherever you like in a type definition
api_label

api_label is used as a field description in the OpenAPI spec. If not given, label is used instead.

For an OpenAPI overview please see OpenAPI (aka Swagger).

Global Entities

You can define entities for action, condition and validator for global use in the corresponding files below workflow.global.. The format is the same as described below, the global_ prefix is added by the system.

Creating Macros (not implemented yet!)

If you have a sequence of states/actions you need in multiple workflows, you can define them globally as macro. Just put the necessary state and action sections as written above into a file below workflow.macros.<name>. You need to have one state named INITIAL and one FINAL.

To reference such a macro, create an action in your main workflow and replace the class atttribute with macro. Note that this is NOT an extension to the workflow engine but only merges the definitions from the macro file with those of the current workflow. After successful execution, the workflow will be in the state passed in the success attribute ofthe surrounding action.

Workflow UI Rendering

The UI uses information from the workflow definition to render display and input pages. There are two different kinds of pages, switches and inputs.

Action Switch Page

Used when the workflow comes to a state with more than one possible action.

headline

Concated string from state.label + workflow.label

descriptive intro

String as defined in state.description, can contain HTML tags

workflow context

By default a plain dump of the context using key/values, array/hash values are converted to a html list/dd-list. You can define a custom output table with labels, formatted values and even links, etc - see the section “Workflow Output Formatting” fore details.

button bar / simple layout

One button is created for each available action, the button label is taken from action.label. The value of action.tooltip becomes a mouse-over label.

button bar / advanced layout

If you set the state.hint attribute, each button is drawn on its own row with a help text shown aside.

Form Input Page

Used when the workflow comes to a state where only one action is available or where one action was choosen.

headline

Concated string from action.label (if none is given: state.label ) + workflow.label

descriptive intro

String as defined in action.description, can contain HTML tags

form fields

The field itself is created from label, placeholder and tooltip. If at least one form field has the description attribute set, an explanatory block for the fields is added to the bottom of the page.

Markup of Final States

If the workflow is in a final state, the default is to render a colored status bar on with a message that depends on the name of the state. Recognized names are SUCCESS, CANCELED and FAILURE which generate a green/yellow/red bar with a corresponding error message. The state name NOSTATUS has no status bar at all.

If the state does not match one of those names, a yellow bar saying “The workflow is in final state” is show.

To customize/suppress the status bar you can add level and message to the state definition (see above).

Workflow Output Formatting

Buttons

Key/Value Grid

redirect

Creates an immediate redirect command with value as location. The location must be an ember route, e.g. workflow!start!system_status.

If the value is a hashref, you can also show a status message (message, level) and define a pause interval (pause - in seconds). The redirect target must be in the target key, if not set the redirect target is the current workflow.

WebUI components

Menustructure, search options and some additional items can be configured based on the user role using the files located in the uicontrol/ folder. There can be one file for each role, if no role file is found, the configuration from _default.yaml is loaded. Note that there is no inheritance, so you always need to provide a full file per role.

Format Result List

Those options apply to (almost) all sections that deal with result lists for workflows or certificates.

Column Layout

Add a section cols to you definition block:

cols:
  - label: I18N_OPENXPKI_UI_WORKFLOW_SEARCH_SERIAL_LABEL
    field: WORKFLOW_SERIAL
  - label: I18N_OPENXPKI_UI_WORKFLOW_SEARCH_UPDATED_LABEL
    field: WORKFLOW_LAST_UPDATE
  - label: I18N_OPENXPKI_UI_WORKFLOW_STATE_LABEL
    field: WORKFLOW_STATE
  - label: I18N_OPENXPKI_UI_CERTIFICATE_SUBJECT
    field: context.cert_subject
  - label: I18N_OPENXPKI_UI_WORKFLOW_FIELD_TRANSACTION_ID_LABEL
    template: "[% context.transaction_id %]"

Label and either field or template are mandatory. Optional keys are sortkey, which is required to use sorting together with templates, and format which adds a formatting rule such as “timestamp” or “certstatus” (See the WebUI Page API Reference for all available formats).

Pager

The pager has default settings in the code but you can provide your own values:

pager:
  pagesizes: 10, 20, 50, 100
  pagersize: 5

The pagesizes parameter is responsible for the “Items per page” selector. The pagersite parameter refers to the number of itesm in the page selector.

Certificate search mask

The four fields Subject, Subject. Alt Name, Profile and Status are fixed. You can add additional that are combined to a cloneable dual-select field to search for data in the certificate_attributes tables.

Add a block named “certsearch” to the uicontrol file:

certsearch:
  default:
    attributes:
     - label: I18N_OPENXPKI_UI_WORKFLOW_FIELD_ENTITY_LABEL
       key: meta_entity

     - label: I18N_OPENXPKI_UI_SEARCH_REQUESTOR_NAME_LABEL
       key: meta_requestor
       pattern: '*%s*'
       operator: inlike

     - label: I18N_OPENXPKI_UI_SEARCH_REQUESTOR_EMAIL_LABEL
       key: meta_email
       operator: in
       transform: lower

Label and key are mandatory, key is the attributes key to be found in certificate_attributes, as of v1.19 the default operator is “IN”, so multiple values given for the same key are “ORed” (up to 1.18 this was AND which confused most users).

Possible operators are LIKE and EQUAL (values are ANDed!) or the special “INLIKE” (LIKE pattern with values ORed).

The transform and pattern keyword allow preprocessing of the input values prior passing it to the SQL engine. Transform can be upper or lower which applies the uppercase/lowercase method to the value, pattern is used with sprintf:

$val = sprintf($pattern, $val);

Each transformation is applied individually on each value.

Workflow search mask

This is the same as for the certificate search mask, the uicontrol key is wfsearch, queries are executed against the workflow_attributes table.

Tasklist

The page “My Tasks” can hold multiple blocks showing a list of workflows. Minimal configuration for one item looks like:

tasklist:
  - label: I18N_OPENXPKI_UI_TASKLIST_PENDING_REVOCATION_LABEL
    description: I18N_OPENXPKI_UI_TASKLIST_PENDING_REVOCATION_DESCRIPTION
    query:
      TYPE:
        - certificate_revocation_request_v2
      STATE:
        - PENDING

Label and description are shown on top of the result table, anything below query is handed as parameter to the search_workflow_instances API method.

If the result set of the query is empty, the default behaviour is to display a default “No result” test. You can customize this text using the ifempty parameter:

  • label: I18N_OPENXPKI_UI_TASKLIST_PENDING_ENROLLMENT_LABEL description: I18N_OPENXPKI_UI_TASKLIST_PENDING_ENROLLMENT_DESCRIPTION ifempty: Sorry but there is nothing to do today…

If you dont want to show an empty the result at all, pass the special word hide:

- label: I18N_OPENXPKI_UI_TASKLIST_PENDING_ENROLLMENT_LABEL
  description: I18N_OPENXPKI_UI_TASKLIST_PENDING_ENROLLMENT_DESCRIPTION
  ifempty: hide

Certificate profiles

A certificate profile is the blueprint that determines all technical aspects of the certificate such as subject pattern, key usages and other extensions.

Naming

The internal name of the profile is the name of the node in the configuration layer. If you keep the sample structure each profile is in a single file in the profile directory, so the name of the profile is the name of the file.

You can add label and description to the profile, which is used for display purpose on the WebUI frontend only, it has no effect on the actual certificate.

Validity

The validity is usually defined by a relative time specification of the format +YYMMDDhhmmss, (e.g. +0006 for six month, see OpenXPKI::DateTime):

validity:
    notafter: +0006

The actual value is determined at the moment the PKI really signs the request. You can also add a notbefore date, in this case the notafter date is calculated relative to notbefore:

validity:
    notbefore: +000001
    notafter: +0006

Above example will create a certificate with a notbefore 24 hours ahead of the time of issuance and ends 6 months + 1 day later.

It is also possible to give an absolute date as YYYYMMDDhhmmss.

Styles

t.b.d

Subject and Process Information

OpenXPKI can collect meta information based on the selected profile and has a templating engine to build subject and subject alternative name sections (SAN) from the input data in many different ways.

The input fields are summarized in three groups: subject, san and info:

00_basic_style:
    label: I18N_OPENXPKI_UI_PROFILE_BASIC_STYLE_LABEL
    description: I18N_OPENXPKI_UI_PROFILE_BASIC_STYLE_DESC
    ui:
        subject:
            - hostname
            - hostname2
            - port
        san:
            - san_ipv4
        info:
            - requestor_gname
            - requestor_name
            - requestor_email
            - requestor_affiliation
            - comment

Each section can hold any number of fields, each field is defined by a set of options:

id: hostname
label: I18N_OPENXPKI_UI_PROFILE_HOSTNAME
placeholder: fully.qualified.example.com
tooltip: I18N_OPENXPKI_UI_PROFILE_HOSTNAME_TOOLTIP
description: I18N_OPENXPKI_UI_PROFILE_HOSTNAME_DESC
type: freetext
preset: "[% CN.0.replace(':.*','') %]"
match: \A [A-Za-z\d\-\.]+ \z
width: 60

The definition can be placed in the node template inside the profile file or globally in the template directory.

To not break legacy configurations, the placeholder can be set using “default” as keyword. The tooltip is set to description if missing.

Field Definition
id
the key used when this item is written into the workflow
label
the label shown next to the input field
description
description, shown as tooltip
type
the type of the field, freetext or select
option
list of options for the select field (used value is equal to the label)
preset

in case a CSR is uploaded by the user, you can use parts of it to prefill the fields. Items from the subject can be referenced by the name of the component, items from the subject alternative name section are prefixed with the string SAN_, e.g. SAN_DNS. Note that all keys are uppercased and all items are arrays regardless of the number of items found!

There are several options for preprocessing the items.

First item of type CN:

preset: CN.0

All items of type OU (creates on field per item):

preset: OU.X

Use templating to extract left side of CN up to the first colon:

preset: "[% CN.0.replace(':.*','') %]"

Use templating to create a list of items, the pipe symbol is used as seperator:

preset: "[% FOREACH ou = OU %][% ou %]|[% END %]"
match
a regex pattern that is applied to the user input for validation
width
size of the field - not implemented yet, definition might change.
placeholder
A text which is shown as placeholder in the input field (this value is NOT a default value for the field)
renew

How to handle this field during a certificate renewal request. Can be one of:

keep: the field is set to the existing value and can not be changed

preset: the field is set to the existing value but can be changed

clear: the current value is unset
Subject Rendering

The full distinguished name and the Subject Alternative Name items are created using template toolkit rules from the information that have been collected from the input fields in the “subject” step:

subject:
    dn: CN=[% hostname.lower %][% IF port AND port != 443 %]:[% port %][% END %],DC=Test Deployment,DC=OpenXPKI,DC=org
    san:
        DNS:
         - "[% hostname.lower %]"
         - "[% FOREACH entry = hostname2 %][% entry.lower %] | [% END %]"

The name of the variable it the one given as “id” in the field definition, all non empty values are available for DN and SAN rendering.

If you have provided an extra SAN section in the input fields definition, those are merged into the SAN part WITHOUT any parsing “as is”.

Extensions

t.b.d.

Key Parameters

OpenXPKI supports serverside key generation as well as PKCS10 upload. For both cases you can control what algorithms and parameters are allowed, even on a per profile basis. The default configuration has the key definition in the default.yaml file.

Basic definition of allowed key and encryption algorithms:

key:
    # Supported key algorithms (details need to be defined below!)
    alg:
      - rsa
      - ec
      - dsa

    # Supported encryption algorithms (as taken by openssl)
    enc:
      - aes256
      - _3des
      - idea

    # one of escrow, server, client, both
    # escrow is not implemented in workflows, yet!
    generate: both

For RSA and DSA, you need to define the allowed key sizes in bits:

rsa:
key_length:
  • 2048
  • 4096
  • _1024

Those values are used for the key generation dialog as well as for the validation of uploaded PKCS10 files. Values with an underscore are hidden from the UI.

For ECC, you need to specify the curve names:

ec:
    curve_name:
      - prime256v1
      - secp384r1
      - secp521r1

The possbile “named” curves are limited by the ones supported by Crypt::PKCS10 at the moment. For NIST P-192/256 you can use either the secpXXXr1 or primeXXXv1 alias.

Enrollment Workflow

The enrollment workflow is used by the SCEP, EST and RPC interface. The details how to connect the workflow to the API differ but the general configuration and operational modes are the same for all three subsystems.

Operational Modes

It is important to understand that the default workflow has three operational modes which are autodetected based on parameters of the request. It is a common problem that the workflow does not behave as excepted because you got into the wrong operational mode due to a non-compliant client (e.g Cisco ASA).

Initial Enrollment

Anonymously request a certificate for the first time - requires that the SCEP request is self-signed, which means the certificate used for the outer signature must match the key of the CSR and the subject of the request must match the subject of the signer certificate (which is a self-signed certificate in this case). Especially Ciso ASA fails here as the certificate subject does not match the request subject.

For EST and RPC this is equal to an unauthenticated/unsigned TLS request.

Renewal

Request renewal by sending a new request signed with the existing certificate. This requires that the full subject of request and signer certificate matches! With the default profiles the PKI will enforce the DC/O parts of the entity certificates so this is a common problem when new certificates are created using the old configuration. Best strategy is to create the new request from the old certificate to ensure the subjects match. Please note that reuse of keys is not supported, you must generate a new key for each new request.

As you can not use a TLS server certificate to authenticate as client the workflow supports an approach names “surrogate certifcates” which requires that you create a look-alike self-signed certificate with the proper key usage. Have a look at the perldoc of the EvaluateSignerTrust activity for more details.

Enrollment On Behalf

Request a certificate with the help of a Trusted Third Party - the request is signed using a certificate issued from the PKI which is qualified as “Authorized Signer (see below)” for the given endpoint. This branch is always choosen if the subject of request and signer do not match, so it is often hit by accident when Renewal or Initial Enrollment are made with “wrong” subjects.

For SCEP the signature on the SCEP PKCS7 container is the TTP, for EST/RPC the TTP is the TLS client certificate used to make the connection.

Workflow Logic

The workflow validates incoming requests against five stages. Parameters are described in detail in the section on policy settings.

technical parameters:
Check if key algorithm, key size and hash algorithm match the policy. If any of those checks fails, the request is rejected.
authentication:
A request can either be self-signed and provide a challenge password or use an HMAC for authentication or is signed by a trusted certificate (renewal or “signer on behalf”). You can also disable authentication or dispatch unauthorized requests to an operator for review.
subject duplicate check:
The database is checked for valid certificates with the same subject. If issuing the certificate would exceed the configured maximum count, the request is dispatched to an operator wo can either reject the request or take actions to meet the policy.
eligibility:
The basic idea is to check requests based on the subject or additonal parameters against an external source to see if enrollment is possible. The check counts against the approval point counter, the workflow does not take any special action if the check fails.
approval point:
The first four stages are usually run in one step when the request hits the server. Before the certificate is issued, the request must have a sufficient number of approval points. Each operator approval is worth one point. A passed eligibility check is also worth one.

Sample Configuration

The workflow fetches all information from the configuration system at <subsystem>.<servername> where the servername is taken from the wrapper configuration.

Here is a complete sample configuration (found in scep/generic.yaml). The sections token, workflow and response are only used by SCEP, the export_certificate is only applicable to the RPC output. The remainder of the configuration is the same for all subsystems:

# By default, all scep endpoints wll use the default token defined
# by the scep token group, if you pass a name here, it is considered
# a group name from the alias table
#token: ca-one-special-scep

workflow:
    type: certificate_enroll
    param:
        # key: name in workflow context, value: parameter from scep wrapper
        # server and interface are always set, the mapping below is
        # the default set that is used when no map is given
        transaction_id: transaction_id
        signer_cert: signer_cert
        pkcs10: pkcs10
        _url_params: url_params
        #_pkcs7: pkcs7

response:
    # The scep standard is a bit unclear if the root should be in the chain
    # or not. We consider it a security risk (trust should be always set
    # by hand) but as most clients seem to expect it, we include the root
    # by default.
    # The getca response contains the certificate of the SCEP server itself
    # and of the current active issuer (which can but need not to be the same!)
    # You can define weather to have only the certificate itself (endentity),
    # the chain without the root (chain)  or the chain including the root
    # (fullchain).
    # Note: The response is cached internally in the datapool so changes
    # will not show up immediately - to list the cached items use
    # openxpkicli list_data_pool_entries  --arg namespace=scep.cache.getca
    # You can delete by setting the empty string as value with
    # set_data_pool_entry (value="" force=1)
    getca:
        ra:     fullchain
        issuer: fullchain

# A renewal request is only accpeted if the used certificate will
# expire within this period of time.
renewal_period: 000060

# If the request was a replacement, optionally revoke the replaced
# certificate after a grace period
revoke_on_replace:
    reason_code: keyCompromise
    delay_revocation_time: +000014

authorized_signer:
    rule1:
        # Full DN
        subject: CN=.+:pkiclient,.*
    rule2:
        # Full DN
            subject: CN=my.scep.enroller.com:generic,.*

policy:
    # Authentication Options
    # Initial requests need ONE authentication.
    # Activate Challenge Password and/or HMAC by setting the appropriate
    # options below.

    # if set requests can be authenticated by an operator
    allow_man_authen: 1

    # if set, no authentication is required at all and hmac/challenge is
    # not evaluated even if it is set/present in the request!
    allow_anon_enroll: 0

    # Approval
    # If not autoapproved, allow opeerator to add approval by hand
    allow_man_approv: 1

    # if the eligibiliyt check failed the first time
    # show a button to run a recheck (Workflow goes to PENDING)
    allow_eligibility_recheck: 0

    # Approval points requirede (eligibity and operator count as one point each)
    # if you set this to "0", all authenticated requests are auto-approved!
    approval_points: 1

    # The number of active certs with the same subject that are allowed
    # to exist at the same time, deducted by one if a renewal is seen
    # set to 0 if you dont want to check for duplicates at all
    max_active_certs: 1

    # option will be removed
    # allow_expired_signer: 0

    # If an initial enrollment is seen
    # all existing certificates with the same subject are revoked
    auto_revoke_existing_certs: 1

    # allows a "renewal" outside the renewal window, the notafter date
    # is aligned to the old certificate. Set revoke_on_replace option
    # to revoke the replaced certificate.
    # This substitutes the "replace_window" from the OpenXPKI v1 config
    allow_replace: 1

    # by default only the certificate identifier is written to the workflow
    # set to a true value to get the PEM encoded certificate in the context,
    # set to "chain" to get the issuer certificate and "fullchain" to get
    # the chain including the root certificate (key chain).
    export_certificate: chain

profile:
  cert_profile: tls_server
  cert_subject_style: enroll

# Mapping of names to OpenXPKI profiles to be used with the
# Microsoft Certificate Template Name Ext. (1.3.6.1.4.1.311.20.2)
profile_map:
    pc-client: tls_client

# HMAC based authentication
hmac: verysecret

# see below how to get a per-request password
challenge:
    value: SecretChallenge

eligible:
    initial:
       value@: connector:scep.generic.connector.initial
       args: '[% context.cert_subject_parts.CN.0 %]'
       expect:
         - Build
         - New

    renewal:
       value: 1


connector:
    initial:
        class: Connector::Proxy::YAML
        # this file must have a key/value list with the key being
        # the subject and the value being a true value
        # e.g. "pc1234.example.org: 1"
        LOCATION: /home/pkiadm/cmdb.yaml

The renewal period values are interpreted as OpenXPKI::DateTime relative date but given without sign.

Upgrade from OpenXPKI v1 enrollment workflow

If you are upgrading from OpenXPKI 1.x enrollment workflow to the new one, you must adjust several parameters in the scep server configuration.

renewal/replace period

The logic for replace has changed, replace is now always assumed when you are outside the renewal period:

# old syntax
renewal_period: 000014
replace_period: 05

# new syntax
renewal_period: 000014

# note that the policy node already exists!
policy:
    allow_replace: 1

signer on behalf

The name of the key has changed from authorized_signer_on_behalf to authorized_signer only:

# old syntax
authorized_signer_on_behalf:
    rule1:
        ......

# new syntax
authorized_signer:
    rule1:
        ......

profile definition

In OpenXPKI 1.0 the default profile was set in the CGI wrapper configuration. This has been moved to a seperate node in the endpoint configuration:

profile:
    cert_profile: tls_server
    cert_subject_style: enroll

key_checks

Are now read from the profiles, so there is no longer an extra definition in the workflow.

Workflow Configuration

Test-Drive (INSECURE)

If you need a server that just creates certificates, use the following policy section:

policy:
    allow_anon_enroll: 1
    approval_points: 0
    max_active_certs: 0
    allow_replace: 0
    export_certificate: chain

This will issue any certificate for any request - so do not use this in production

Authentication
Signer on Behalf

The section authorized_signer is used to define the certificates which are accepted to do a “request on behalf”. The list is given as a hash of hashes, were each entry is a combination of one or more matching rules.

Possible rules are subject, profile and identifier which can be used in any combination. The subject is evaluated as a regexp against the signer subject, therefore any characters with a special meaning in perl regexp need to be escaped! Identifier and profile are matched as is. The rules in one entry are ANDed together. If you want to provide alternatives, add multiple list items. The name of the rule is just used for logging purpose.

Challenge Password

The request must carry the password in the challengePassword attribute. The sample config above shows a static password example but it is also possible to use request parameters to lookup a password using connectors:

challenge:
   mode: bind
   value@: connector:scep.connectors.challenge
   args:
   - "[% context.cert_subject %]"

connectors:
    challenge:
        class: Connector::Builtin::Authentication::Password
        LOCATION: /home/pkiadm/democa/passwd.txt

This will use the cert_subject to validate the given password against a list found in the file /home/pkiadm/democa/passwd.txt. For more details, check the man page of OpenXPKI::Server::Workflow::Activity::Tools::ValidateChallengePassword

Renewal/Replace

A request is considered to be a renewal if the request is not self-signed but the signer subject matches the request subject. Renewal requests pass authentication if the signer certificate is valid in the current realm and neither revoked nor expired. You can allow expired certificates by setting renewal.notafter (Not implemented yet!).

Manual Authentication

If you set the allow_man_authen policy flag, request that fail any of the above authentication methods can be manually authenticated via the UI.

No Authentication

To completly skip authentication, set allow_anon_enroll policy flag.

Subject Checking

The policy setting max_active_certs gives the maximum allowed number of valid certificates sharing the same subject. If the certificate count after issuance of the current request will exceed this number, the workflow stops in the PENDING_POLICY_VIOLATION state. If this parameter is not set, no checks are done. There are several settings that influence this check, based on the operation mode:

Initial Enrollment

If you set the auto_revoke_existing_certs policy flag, all certificates with the same subject will be revoked prior to running this check. This does not make much sense with max_active_certs larger than 1 as all certificates will be revoked as soon as a new enrollment is started! The intended use is replacement of broken systems where the current certificate is no longer used anyway.

Renewal/Replace

If the request is a renewal or replacement request, it is allowed to exceed the max_active_certs by one.

Eligibility

The default config has a static value of 1 for renewals and 0 for initial requests. If you set approval_points to 1, this will result in an immediate issue of certificate renewal requests but requires operator approval on initial enrollments.

Assume you want to use an ldap directory to auto approve initial requests based on the mac address of your client:

eligible:
    initial:
        value@: connector:your.connector
        args:
        - "[% context.cert_subject %]"
        - "[% context.url_mac %]"

connectors:
    devices:
        ## This connector just checks if the given mac
        ## exisits in the ldap
        class: Connector::Proxy::Net::LDAP::Simple
        LOCATION: ldap://localhost:389
        base: ou=devices,dc=mycompany,dc=com
        filter: (macaddress=[% ARGS.1 %])
        binddn: cn=admin,dc=mycompany,dc=com
        password: admin
        attrs: macaddress

To have the mac in the workflow, you need to pass it with the request as an url parameter to the wrapper: http://host/scep/scep?mac=001122334455.

For more options and samples, see the perldoc of OpenXPKI::Server::Workflow::Activity::Tools::EvaluateEligibility

Approval

A request is approved if it reaches the number of approvals defined by the approval_points policy setting. As written above, you can use a data source to get one approval point via the eligibility check. If a request has an insufficient number of approvals, the workflow will stop and an operator must give an approval using the WebUI. By raising the approval points value, you can also enforce a four-eyes approval. If you do not want manual approvals, set the policy flag allow_man_approv to zero - all requests that fail the eligibility check will be immediately rejected.

Certificate Configuration

SCEP Server Token

This is the cryptographic token used to sign and decrypt the SCEP communication itself. It is not related to the issuing process of the requested certificates!

The crypto configuration of a realm (crypto.yaml) defines a default token to be used for all scep services inside this realm. In case you want different servers to use different certificates, you can add additional token groups and reference them from the config using the token key.

The value must be the name of a token group, which needs to be registered as an anonymous alias:

openxpkiadm alias --realm democa --identifier <identifier> --group democa-special-scep --gen 1

Note that you need to care yourself about the generation index. The token will then be listed as anonymous group item:

openxpkiadm alias --realm democa

=== anonymous groups ===
democa-special-scep:
  Alias     : democa-special-scep-1
  Identifier: O9vtjge0wHpYhDpfko2O6xYtCWw
  NotBefore : 2014-03-25 15:26:18
  NotAfter  : 2015-03-25 15:26:18
Profile Selection / Certificate Template Name Extension

This feature was originally introduced by Microsoft and uses a Microsoft specific OID (1.3.6.1.4.1.311.20.2). If your request contains this OID and the value of this oid is listed in the profile map, the workflow will use the given profile definition to issue the certificate. If no OID is present or the value is not in the map, the default profile from the server configuration is used. This map is also used if the you pass the profile as parameter in an RPC call.

The map is a hash list:

profile_map:
    tlsv2: tls_server_v2
    client: tls_client
Subject Rendering

Subject rendering is based on the profile and subject information given in the config:

profile:
    cert_profile: tls_server
    cert_subject_style: enroll

The subject will be created using Template Toolkit with the parsed subject hash as input vars. The vars hash will use the name of the attribute as key and pass all values as array in order of appearance (it is always an array, even if the attribute is found only once!). You can also add SAN items but there is no way to filter or remove san items that are passed with the request, yet.

Example: The default TLS Server profile contains an enrollment section:

enroll:
    subject:
        dn: CN=[% CN.0 %],DC=Test Deployment,DC=OpenXPKI,DC=org

The issued certificate will have the common name extracted from the incoming request but get the remaining path compontens as defined in the profile.

Revoke on Replace

If you have a replace request (signed renewal with signer validity outside the renewal window), you can trigger the automatic revocation of the signer certificate. Setting a reason code is mandatory, supported values can be taken from the openssl man page (mind the CamelCasing), the delay_revocation_time is optional and can be relative or absolute date as consumed by OpenXPKI::DateTime, any empty value becomes “now”:

revoke_on_replace:
    reason_code: superseded
    delay_revocation_time: +000002

The above gives your friendly admins a 48h window to replace the certificates before they show up on the next CRL.

Note: Without any other measures, this will obviously enable an attacker who has access to a leaked key to obtain a new certificate. We used this to replace certificates after the Heartbleed bug with the scep systems seperated from the public network.

WebUI Page API Reference

The web pages are created (mainly) on the client from a JSON control stucture delivered by the server. This document describes the structure expected by the rendering engine.

Top-Level Structure

This is the root element of any json result:

%structure = (
    page => { TOP_LEVEL_INFO },
    right => [ PAGE_SECTION, PAGE_SECTION,...] , # optional, information which will be displayed in additional right pane
    main => [ PAGE_SECTION, PAGE_SECTION,...] , # information which will be displayed in the main section
    reloadTree => BOOL (1/0), # optional, the browser will perform a complete reload. If an additional "goto" is set, the page-url will change to this target
    goto => STRING PAGE, # optional, will be evaluated as url-hashtag target
    status => { STATUS_INFO } # optional
);

Example { reloadTree => 1, goto => 'login/login' }
Page Head (TOP_LEVEL_INFO):

This is rendered as the page main headline and intro text.

TOP_LEVEL_INFO:
{
    label => STRING, #Page Header
    description => STRING, # additional text (opt.)
}

Example:

page => { label => 'OpenXPKI Login', description => 'Please log in!' }
Status Notification (STATUS_INFO):

Show a status bar on top of the page, the level indicates the severity and results in different colors of the status bar.

STATUS_INFO:
{
    level => STRING, # allowed values: "info", "success","warn", "error"
    message => STRING # status message shown
}

Example:

status => { level => 'error', message => 'Login credentials are wrong!' }

Page Level

The page sections (main and right) can hold multiple subpage definitions. The main section must always contain at least one section while right can be omitted or empty.

Page Section (PAGE_SECTION)

This is the top level container of each page section.

PAGE_SECTION:
{
    type => STRING # see SECTION-TYPE below for supported types
    content => {
        label => STRING # optional, section headline
        description => STRING , # optional, additional text (html is allowed)
        buttons => [ BUTTON_DEF, BUTTON_DEF, ... ] , # optional, defines the buttons/links for this section
        # additional content-params depending on type (see below)
    },
    # additional section-params depending on type:
}
SECTION-TYPE “text”

Print the label as subheadline (h2) and description as intro text, buttons are rendered after the text. Does not have any additional parameters. Note: If you omit label and description this can be used to render a plain button bar or even a single button.

SECTION-TYPE “grid”

Grids are rendered using the jquery datatable plugin (http://datatables.net). The grid related parameters are just pushed to the dataTables engine and therefore have a different notation and syntax used as the remainder of the project.

content => {
    label => ..,
    description => ..,
    buttons => [ BUTTON_DEF, BUTTON_DEF, ... ] , # optional, defines the buttons/links for this grid
    columns => [ GRID_COL_DEF, GRID_COL_DEF , GRID_COL_DEF... ],
    data => [ GRID_ROW, GRID_ROW, GRID_ROW, ... ],
    actions => [ GRID_ACTION_DEF, GRID_ACTION_DEF, GRID_ACTION_DEF... ], # defines available actions, displayed as context menu
    processing_type => STRING, # only possible value (for now) is "all"
}

GRID_COL_DEF:
{
    sTitle => STRING, # displayed title of that columnd AND unique key
    format => STRING_FORMAT # optional, triggers a formatting helper (see below)
}

GRID_ROW:
    ['col1','col2','col3']


GRID_ACTION_DEF:
{
    page => STRING_PATH, # calls an OpenXPKI page. Terms enclosed in {brackets} will be evaluated as column-keys and replaced with the value of the given row for that column
    action => STRING_PATH, # calls an OpenXPKI action. Replacements work as in "page".
    href => STRING_PATH, # opens a webpage. Replacements work as in "page".
    label => STRING, # visible menu entry
    target => STRING_TARGET # optional, where to open the new page, one of self|top|popup
    icon => STRING , # optional, file name of image icon, must be placed in htdocs/img/contextmenu
}

Columns, whose sTitle begin with an underscore will not be displayed but used as internal information (e.g. as path in GRID_ACTION_DEF). A column with the special title _status is used as css class for the row. Also a pulldown menu to filter by status will be displayed. The rows hold the data in form of a positional array.

Action target popup creates a modal popup.

Example:

content => {
    columns => [
    { sTitle => "Serial" },
        { sTitle => "Subject" },
    { sTitle => "date_issued", format => 'timestamp'},
    { sTitle => "link", format => 'link'},
    { sTitle => "_id"}, # internal ID (will not be displayed)
    { sTitle => "_status"}, # row status
    ],
    data => [
        ['0123','CN=John M Miller,DC=My Company,DC=com',1379587708, {page => 'http://../', label => 'Click On Me'}, 'swBdX','issued'],
        ['0456','CN=Bob Builder,DC=My Company,DC=com',1379587517,{...},'qqA2H','expired'],
    ],
    actions => [
        {
            page => 'cert!detail!{_id}',
            label => 'Details',
            icon => 'view',
            target => 'popup'
        },
        {
            page => 'cert!mail2issuer!{email}',
            label => 'Send an email to issuer'
        },
    ]
}
SECTION-TYPE “form”

Render a form to submit data to the server

content => {
    label => STRING,
    description => STRING,
    buttons => [ BUTTON_DEF, BUTTON_DEF, ... ], # a form must contain at least one button to be useful
    fields => [ FORM_FIELD_DEF, FORM_FIELD_DEF, ... ],
}

FORM_FIELD_DEF:
{
    name => STRING # internal key - will be transmitted to server
    value => MIXED, # value of the field, scalar or array (depending on type)
    label => STRING, # displayed label
    type => STRING_FIELD_TYPE, # see FIELD-TYPE below for supported types
    is_optional => BOOL, # if false (or not given at all) the field is required
    clonable => BOOL, # creates fields that can be added more than once
    visible => BOOL, # if set to "false" ("0" in perl) this field will be not displayed (initial)
    keys => ARRAY, # optional, activates the special feature of "dynamic key value fields", see below.
    # + additional keys depending for some types
}
FIELD-TYPE “text”, “hidden”, “password”, “textarea”

No additional parameters, create a simple html form element without any extras.

FIELD-TYPE “static”

No additional parameters, creates a simple “readonly” text element with the value treated as a “hidden” form element. If you want to display a formatted version of the value instead, you can pass it using the verbose key.

FIELD-TYPE “checkbox/bool”

A html checkbox; value and is_optional are without effect, as always 0 or 1 is send to the server.

FIELD-TYPE “date”

A text field with a jquery datapicker attached. Additional (all optional) params are:

FORM_FIELD_DEF:
{
    notbefore => INTEGER, # optional, unixtime, earliest selectable date
    notafter => INTEGER, # optional, unixtime, earliest selectable date
    return_format => STRING # one of terse|printable|iso8601|epoch, see OpenXPKI::Datetime
}
FIELD-TYPE “select”

A html select element, the options parameter is required, others are optional:

FORM_FIELD_DEF:
{
    options => [{value=>'key 1',label=>'Label 1'},{value=>'key 2',label=>'Label 2'},...],
    prompt => STRING # first option shown in the box, no value (soemthing like "please choose")
    editable => BOOL # activates the ComboBox,
    actionOnChange => STRING_ACTION # if the pulldown is changed by the user (or an initial value is given), server will be called with this "action". See "Dynamic form rendering" for details.
}

The options parameter can be fetched via an ajax call. If you set options => 'fetch_cert_status_options', an ajax call to “server_url.cgi?action=fetch_cert_status_options” is made. The call must return the label/value list as defined given above.

Setting the editable flag to a true value enables the users to enter any value into the select box (created with Bootstrap Combobox).

FIELD-TYPE “radio”

The radio type is the little brother of the select field, but renders the items as a list of items using html radio-buttons. It shares the syntax of the options field with the select element:

FORM_FIELD_DEF:
{
    options => [{....}] or 'ajax_action_string'..
    multi => BOOL, # optional, if true, uses checkbox elements instead radio buttons
}
FIELD-TYPE “upload”

Renders a field to upload files with some additional benefits:

FORM_FIELD_DEF:
{
    mode => STRING, # one of hidden, visible, raw
    allowedFiles => ARRAY OF STRING, # ['txt', 'jpg'],
    textAreaSize => {width => '10', height => '15'},
}

By default, a file upload button is shown which loads the selected file into a hidden textarea. Binary content is encoded with base64 and prefixed with the word “binary:”. With mode = visible the textarea is also shown so the user can either upload or paste the data (which is very handy for CSR uploads), the textAreaSize will affect the size of the area field. With mode = raw the element degrades to a html form upload button and the selected file is send with the form as raw data.

AllowedFiles can contain a list of allowed file extensions.

Dynamic key value fields

If a field is defined with the property “keys”, a pulldown of options is displayed above the actual field. This allows the user to specify, which kind of information he wants to specify. The content of the actual field will be submitted to the server with the selected key in the key-pulldown.

Example:

{ name => '...', label => 'Dyn Key-Value', 'keys' => [{value=>"key_x",label=>"Typ X"},{value=>"key_y",label=>"Typ Y"}], type => 'text' },

This example definition will render a Textfield with label “Dyn Key-Value”. Above the textfield a select is displayed with three options (“Typ x”,”Typ y” and “Typ z”). If the user chooses “Typ Z”, the entered value in the textfield will be posted to server with key “key_z”.

This feature makes often more sense in combination with “clonable” fields.

Dynamic form rendering

If a select field is defined with the property “actionOnChange”, each change event of this pulldown will trigger an submit of all formvalues (without validity checks etc) to the server with key “action” set to the value of “actionOnChange”.

Partial redefinition:

The key “fields” is expected in the returned JSON-Structure. “fields” contains an array, which is semantically identic to the key “fields” in the definition of the form. This array “fields” must contain only only the fields (and properties), which should react to the change of the (master-)field (pulldown) . The property “name” is required (otherwise the client can not identify the field). The property “type” can not be subject to changes. With aid of the property “visible” one can dynamically show or hide some fields. Only known fields (which are already defined in the initial “fields”-property of the form-section) can be subject of the “partial” re-rendering. Its not possible to add new fields here.

You can define more than one (cascading) dependent select.

Example:

Initial definition of fields:
fields => [
    { name => 'cert_typ', label => 'Typ',value=> 't2', prompt => 'please select a type', type => 'select', actionOnChange => 'test_dep_select!change_type', options => [{value=>'t1',label=>'Typ 1'},{value=>'t2',label=>'Typ 2'},{value=>'t3',label=>'Typ 3'}] },
    { name => 'cert_subtyp', label => 'Sub-Type', prompt => 'first select type!', type => 'select', options => [] },
    { name => 'special', label => 'Special (only type 2)', type => 'checkbox', visible => 0 },
]
Action “test_dep_select!change_type” returns a (partially updated) definition of fields:
{
    fields => [
        { name => 'cert_subtype', options => [{value=>'x', label => 'Subtype X'}, ...], value => 'x' } ,
        { name => 'special', visible=> 1 }
    ]
};

Full redefinition:

Is not implemented yet.

SECTION-TYPE “key-value”

Render a list of key/value items in a two column grid. The left column shows the text given by label, the right column is formated based on value and format (see Formatted Strings).

There is a special format type head which renders a table head tag spanning both columns. If a context item is referenced, value is used as headline, it might be decorated using a template. As an alternative, a fixed value can be given using the key label.

Add an item with format set to spacer to create an empty separator line (there is a global workflow field named spacer in the default ca-one config so you can just say - spacer in the workflow output section).

An option className can be set which is put into the rows’ <tr> tag.

Item Level

Buttons (BUTTON_DEF)

Defines a button. There are three modes, depending on which one of these parameters is specified: page, action, href.

Common parameters for page and action:

{
    label => STRING, # The label of the button
    tooltip => STRING, # (optional)
    className => STRING, # CSS class (optional)
    confirm => {
        label => STRING, #
        description => STRING, #
        confirm_label => STRING,  # (optional, defaults to "Confirm")
        cancel_label => STRING,  # (optional, defaults to "Abort")
    },
}

target: determines where the contents returned by the server shall be displayed:

  • main - as main tab (close all other tabs)
  • popup - as a modal popup
  • tab - in a new tab
  • active - in the active tab

page: load a page (GET request, no client-side parameters)

::
{
page => STRING, # page to render (GET request with parameter “page”) # + Common parameters (see above)

}

This calls an init_* method in the specified class.

action: execute action via AJAX (POST request with client-side parameters)

::
{
action => STRING, # action to execute (POST request with parameter “action”) # + Common parameters (see above)

}

This calls an action_* method in the specified class.

href: open a custom URL:

{
    href => STRING, # URL to call
    target => STRING, # any HTML link target, e.g. '_blank'
    label => STRING, # The label of the button
    tooltip => STRING, # (optional)
    className => STRING, # CSS class (optional)
}
Formatted Strings (STRING_FORMAT)

Tells the UI to process the data with a special formatter before rendering. Available methods are:

timestamp

Expects a unix timestamp and outputs a full UTC timestamp.

datetime

Expects a parseable date, outputs a full UTC timestamp.

certstatus

Colorizes the given status word using css tags:

{
    label => STRING, # text to show
    value => STRING, # used alternatively to assemble CSS class (optional)
    tooltip => STRING, # (optional)
}

The CSS class is assembled as follows: certstatus- + value. If no value is given: certstatus- + label. E.g. label => "issued" becomes:

<span class="certstatus-issued">issued</span>
text

Readable text without html markup (special characters will be escaped)

nl2br

Like text but with line breaks \n converted to <br>

raw

Displays the given text as is (i.e. HTML formatting allowed).

code

Rendered with fixed-with typo, unix linebreaks are converted to html linebreaks.

deflist

Outputs a key/value list (dl/dt/dd) - expects an array where each item is a hash with keys key and value.

ullist

Array of values, each item becomes a <li> in the list, values are html-escaped.

rawlist

Like ullist but displays the items “as is” (can contain HTML markup)

styled

Expects a value in the format stylename:Text to display. The part left of the colon is extracted and the text at the right is wrapped with span with style class “styled-stylename. Predefined stylenames are valid, failed and attention

tooltip

Shows the given text as is (i.e. HTML formatting allowed) and adds a tooltip to it:

{
    value => STRING, # text to be shown
    tooltip => STRING, # text of the tooltip
}

Customization

The framework allows to register additional components via an exposed api.

Form-Field

Add a new FormField-Type:

OXI.FormFieldFactory.registerComponent('type','ComponentName',JS_CODE [,bOverwriteExisting]);

Example:

OXI.FormFieldFactory.registerComponent('select','MySpecialSelect', OXI.FormFieldContainer.extend({
    ....
}), true);

This will overwrite the handler for the select element. The ComponentName will be registered in the OXI Namespace and can be used to call the object from within userdefined code.

Formatter

Add a new Format-Handler:

OXI.FormatHelperFactory.registerComponent('format','ComponentName',JS_CODE [,bOverwriteExisting])

Automation / External APIs

Subsystems providing External APIs

OpenXPKI comes with a set of subsystems that can be used to search for certificates and handle workflows using different established or custom APIs.

The most common one is the SCEP interface that supports enrollment of certificates using the “Simple Certificate Enrollment Protocol” which was developed by Cisco and is widely used in hardware devices.

A generic SOAP and RPC wrapper is also available which can be used to implement arbitrary workflows. Some samples are already included in the default config.

If you want connect your Microsoft Windows environment to OpenXPKI, have a look on the “Certificate Enrollment Proxy” (https://www.secardeo.de/produkte/certep/). This commercial product is a snap-in that routes certificate requests from a Microsoft CA to OpenXPKI, enabling approval workflows and certificate lifecycle management for Windows environments.

Wrapper Configuration

All wrappers are implemented as a fast-cgi script with the default webserver handling the HTTP layer, talking to the OpenXPKI daemon using the existings socket. The basic configuration pattern is the same for all subsystems, just replace the rpc used in the given samples with the name of the wrapper.

Each wrapper has a directory of the same name in the openxpki main config folder (e.g. /etc/openxpki/rpc) holding the global default and the logger config at least. Currently, only the Config::Std format is supported for those files!

Global Default Config

The name of the global config file must be default.conf and consists of three section holding information on logger, auth and socket:

[global]
socket = /var/openxpki/openxpki.socket

[logger]
log_level = WARN

[auth]
stack = _System

socket Full path of the OpenXPKI socket file

log_level A Log4perl log level, the logger will be auto created to use a Log4perl Appender with a logfile at /var/log/openxpki/<servicename>.log.

stack Name of the authentication stack, default is to connect as Anonymous, all additional attributes are passed unaltered to the authentication layer. See OpenXPKI::Client::Simple.

Alternative logger configuration (default before v3.22)

If you want to have more control over the logger, omit the logger section, create a Log4perl configuration file and add it in the I<global> section:

[global] ….other setting…. log_config = /etc/openxpki/rpc/log.conf log_facility = client.rpc

log_config must point to the Log4perl configuration file that should be used by this wrapper. If the file is not found or the option is missing, a default logger writing to STDERR is used.

log_facility The facility name to log with, this is useful if you want to log to the same file from multiple different systems.

See OpenXPKI::Client:Config for more details on logger configuration.

Config Path Expansion

If you run the cgi wrapper scripts using the provided Alias rules, you can have multiple named configurations. Call the wrapper using an alias path like http://host/rpc/vpnclient to load the config information from /etc/openxpki/rpc/vpnclient.conf.

If no file is found, the default config is loaded from /etc/openxpki/scep/default.conf. The wrapper uses SCRIPT_URL or REQUEST_URI from the apache environment to extract the requests path, this should work in most environments with mod_rewrite, symlinks or alias definitions. If you use another webserver then apache, you might need to adjust the autodetection rules to fit your needs.

Note The wrappers run as persistent scripts and are initialized before the alias path is known. The socket and logger config is therefore always read from the default.conf!

custom base directory

The value of OPENXPKI_CLIENT_CONF_DIR overwrites the default of the top level configuration folder /etc/openxpki. If set, the service is config is loaded from OPENXPKI_CLIENT_CONF_DIR/<servicename>.

custom service directory

Set OPENXPKI_RPC_CLIENT_CONF_DIR to a directory path. The autodetection will now use this path to find either the special or the default file. Note that there is no fallback to the default location!

fixed file

Set OPENXPKI_RPC_CLIENT_CONF_FILE to an absolute file path. On apache, this can be combined with location to set a config for a special script:

<Location /cgi-bin/rpc/mailgateway>
   SetEnv OPENXPKI_SCEP_CLIENT_CONF_FILE /home/mailadm/rpc.conf
</Location>

Log4perl Config

The default Log4perl config shipped with the sample config file looks like:

log4perl.category.client.rpc = INFO, Logfile

log4perl.appender.Logfile  = Log::Log4perl::Appender::File
log4perl.appender.Logfile.filename = /var/log/openxpki/rpc.log
log4perl.appender.Logfile.layout   = Log::Log4perl::Layout::PatternLayout
log4perl.appender.Logfile.layout.ConversionPattern = %d %p:%P %m%n
log4perl.appender.Logfile.syswrite  = 1

Note: The wrappers run in the context and with permissions of the webserver! You need to make sure that the directory or preexisting files have appropriate permission to be written/created by this user/group!

Webserver Config

Config Path Expansion

The most convenient way to enable the path expansion is to use the Alias directive:

# Same for RPC
ScriptAlias /rpc  /usr/lib/cgi-bin/rpc.fcgi

<Directory "/usr/lib/cgi-bin/">
    AllowOverride None
    Options +ExecCGI
    Order allow,deny
    Allow from all
    # Remove this line if you are using apache 2.2
    Require all granted
</Directory>
TLS Client Authentication

All wrappers except SCEP support authentication using TLS client certificates. The recommended way is to let apache do the TLS handshake but pass the full client certificate to OpenXPKI:

SSLVerifyClient optional
SSLVerifyDepth  3
SSLCACertificateFile /etc/apache2/ssl/root.pem

SSLOptions +FakeBasicAuth +ExportCertData +StrictRequire
<Directory /usr/lib/cgi-bin>
        SSLOptions +StdEnvVars
</Directory>

This makes the properties and the full certificate as PEM available in the SSL_* environment variables where there are picked up as needed and injected into the workflow engine by the wrappers.

Endpoint Configuration

Most of the workflows used with the external APIs use a common pattern to load endpoint specific settings. The interface type together with the servername is used as base path for config lookups. Note that the servername is given explicit in the wrapper config and can be different from the exposed script name.

A sample RPC endpoint configuration might look like:

[RequestCertificate]
workflow = certificate_enroll
param = pkcs10, comment
output = cert_identifier, error_code
env = signer_cert
servername = vpnclient

The base path for config lookups is now, inside the realm config rpc.vpnclient.

If you want to deploy multiple endpoints with the same configuration you can also replace the explicit servername = vpnclient with env = server which will set the internal servername to the name of the endpoint. When you put your configuration in default.yaml this ends up in an automatic factory where each URI …/rpc/anyname/Method will result in a valid call to the workflow using anyname as base for the configuration lookup.

TLS Client Authorization

A widely used example is the check if a client is authorized to run the workflow based on the provided TLS certificate. Most of the workflows use the OpenXPKI::Server::Workflow::Activity::Tools::EvaluateSignerTrust action class for this which grabs the ruleset from interface.servername.authorized_signer, in our example rpc.vpnclient.authorized_signer:

authorized_signer:
  rule1:
    subject: CN=.+:pkiclient,.*

  rule2:
    profile: vpn_client
    realm: vpn-ca

  rule3:
    identifier: AhElV5GzgFhKalmF_yQq-b1TnWg

The provided certificate is matched against each rule, the check returns true if all conditions of one rule are met. The realm is always set to the current realm if not given explicit. The subject is matched as case-insensitive regex all other attributes are matched as equal strings.

RPC Server API

The RPC Service provides a simple HTTP-Based API to the OpenXPKI backend. The builtin REST Server provides methods to request, renew and revoke certificates. The service is implemented using a cgi-wrapper script.

Server-Side Configuration

Wrapper Configuration

The default wrapper looks for its config file at /etc/openxpki/rpc/default.conf. The config uses plain ini format, a default is deployed by the package:

[global]
socket = /var/openxpki/openxpki.socket
locale_directory: /usr/share/locale
default_language: en_US

[logger]
log_level = WARN

[auth]
stack = _System

[input]
allow_raw_post = 1
parse_depth = 5

[output]
use_http_status_codes=0

The global/auth/logger parameters are described in the common wrapper documentation (Wrapper Configuration). Config path extension and TLS Authentication is supported.

TLS Authentication

In case you want to use TLS Client authentication you must tell the webserver to pass the client certificate to the script. For apache, put the following lines into your SSL Host section:

<Location /rpc>
    SSLVerifyClient optional
    SSLOptions +StdEnvVars +ExportCertData
</Location>

Note: We need the apache just to check if the client has access to the private key of the certificate. Trust and /revocation checking is done inside of OpenXPKI so you can also use “optional_no_ca” if you dont want to deal with setting up the correct chains in apache. Blocking clients on TLS level might be a good idea if your service is exposed to “unfriendly users”.

Input Handling

allow_raw_post

Adds the option to post the parameters as json string as raw http body. As we currently do NOT sanitize the parameters send there is a chnace for an attacker to inject serialized json objects this way! So do NOT set this until you are running in a trusted, controlled environemnt or have other security mechanisms in place.

parse_depth

Maximum allowed recursion depth for the JSON body, the default is 5.

Parameter Handling

Input parameters are expected to be passed either via query string or in the body of the HTTP POST operation (application/x-www-form-urlencoded).

At minimum, the parameter “method” must be provided. The name of the method used must match a section in the configuration file, which must at least contain the name of a workflow.

If you have setup config path expansion, you can append the method as third parameter to the URL instead:

http://demo.openxpki.org/rpc/helpdesk/RevokeCertificateByIdentifier

The default is to return JSON formatted data, if you set the I<Accept> header of your request to “text/plain”, you will get the result as plain text with each key/parameter pairs on a new line.

Example: Revoke Certificate by Certificate Identifier

The endpoint is configured in /etc/openxpki/rpc/enroll.conf with the following:

[RevokeCertificate]
workflow = certificate_revocation_request_v2
param = cert_identifier, reason_code, comment, invalidity_time
env = signer_cert, signer_dn, server
output = error_code

See core/server/cgi-bin/rpc.cgi for mapping additional parameters, if needed.

Certificates are revoked by specifying the certificate identifier:

curl \
    --data "method=RevokeCertificateByIdentifier" \
    --data "cert_identifier=3E9tpLu5qpXarcQHnD1LUNsJIpU" \
    --data "reason_code=unspecified" \
    http://demo.openxpki.org/cgi-bin/rpc.cgi

The response is in JSON format (http://www.jsonrpc.org/specification#response_object). Except for the “id” parameter, the result is identical to the definition of JSON RPC:

{ result: {
    id: workflow_id, pid: process_id, state: workflow_state, proc_state: proc_state
}}

On error, the content returned is:

{ error: { code: 1, message: "Verbose error", data: { id, pid, state } } }

Verbose error might be a readable error message or a I18N… translatable tag. If you set default_language in the wrapper configuration the I18N tags are translated.

Response

By default, the HTTP Status code is always “200 ok” with a numeric error code set in the return structure. The error codes consist of five digits, the first three digits are derived from the HTTP status codes followed by two digits for unambiguousness.

To let the wrapper send the error code on HTTP layer, you need to set:

[output]
use_http_status_codes=1

in the wrapper configuration. This will return 4xx and 5xx status codes together with the above mentioned error structures as body.

For details on the supported error codes see the documentation of the rpc.fcgi wrapper script.

Note: The OpenAPI Spec does not yet return the HTTP status codes.

Workflow Pickup

If you have a workflow that does not return the final result immediately, you can define a search pattern to pickup existing workflows based on worflow_attributes:

[RequestCertificate]
workflow = certificate_enroll
param = pkcs10, comment
output = cert_identifier, error_code, transaction_id
env = signer_cert, enroll
pickup = transaction_id

With a properly prepared workflow, this allows you access an existing workflow based on the transaction_id. For now it is only possible to read existing workflows, there is no option to interact with them, yet.

Examples

The default.conf configuration file defines an endpoint SearchCertificate:

[SearchCertificate]
workflow = certificate_search
param = common_name
output = cert_identifier, notbefore, notafter, status

To utilize this endpoint the following curl command may be used:

$ curl -F "method=SearchCertificate"  -F "common_name=test" http://localhost:8080/rpc

{"result":{"id":0,"data":{"notafter":"2019-04-19T05:21:58","notbefore":"2018-10-19T05:21:58", \
"status":"ISSUED","cert_identifier":"7Da0qfjirGl7PXlZYf9PFVqMJds"},"state":"SUCCESS","pid":915}}

The RequestCertificate endpoint (see above) may be used via:

$ curl -F method=RequestCertificate  -F comment=test -F pkcs10="$(cat certreq.pem)" http://localhost:8080/rpc

{"result":{"id":"5119","state":"SUCCESS","data":{"cert_identifier":"60uHCnC3Uv9wZKjcCkmSHuBwuzU"},"pid":915}}

Of course proper authentication and authorization is required for the cerificate to be issued immediately. The required configuration parameters are documented in the scep workflow.

Retrieving the OpenAPI spec

There is a special RPC method openapi-spec:

$ curl -F "method=openapi-spec" http://localhost:8080/rpc

This will return an OpenAPI compliant specification of all possible OpenXPKI RPC method calls in JSON format.

For an OpenAPI overview please see OpenAPI (aka Swagger).

See Also

See also core/server/cgi-bin/rpc.cgi.

OpenAPI (aka Swagger)

This is not really an own subsystem but it offers an auto-generated OpenAPI 3.0 compliant specification of the RPC interface.

To generate the OpenAPI spec according to your current RPC configuration see Retrieving the OpenAPI spec.

The info block of the specification is by default set to contain a generic title and inherits the version number from system.config.api (as obtained by the version API call). To provide your own values for the info block, add a section [openapi] to the RPC wrapper configuration and set the expected values:

[openapi]
title = Public Certificate Reqest API
description = Request, Renew and Revoke your Certificates her
version = 42.1

The data types of all relevant input/output parameters of those workflows exposed via RPC must be defined (in the workflow config) to be able to generate the OpenAPI spec. For details see OpenAPI specific field parameters.

EST Endpoint / RFC 7030

The default configuration comes with a preconfigured endpoint for the “Enrollment over Secure Transport” Protocol as defined in RFC 7030.

As defined by the protocol the URL is https://<your host>/.well-known/est/, the endpoint maps simple(re)enroll to the certificate_enroll workflow in a similar way as SCEP or RPC. The CACerts and CSRAttrs call is also supported and backed by a workflow that sends sane defaults suitable for most purposes. The wrapper does not support the FullCMC protocol.

The only thing you might need to adjust in the wrapper configuration is the name of the ca realm in case you have more than one. For further details please see the RPC and the common wrapper configuration sections.

The configuration for the default URL is done via the file <myrealm>/est/default.yaml, you can load another configuration by using a calabel which loads the policy from est/<calabel>.yaml.

Default Configuration

The default configuration supports anonymous enrollment with manual approval via UI or automatic issuance using Enrollment on Behalf with a signer certificate.

Smoke Test

If you dont have an EST client at hand, you can easily use curl and openssl:

openssl req -keyout /dev/null -subj "/CN=test me" -nodes -new -newkey rsa:3072 -outform der -out - | base64 > test.pem
curl -v -H "Content-Type: application/pkcs10" --data @test.pem  https://demo.openxpki.org/.well-known/est/simpleenroll

This should return 202 Request Pending - Retry Later (a3...e6c) which indicates that the request was queued for approval. Log into the UI to approve the request and rerun the same line to fetch your certificate. The value in the brackets is the transaction id assigned to this request by the PKI which you can use to find / identify the correct workflow.

Authenticated Test

Use the UI to obtain a TLS Client certificate with the application name pkiclient and add it to the query using the curl options --key/--cert. You should now get your certificate immediately:

openssl req -keyout /dev/null -subj "/CN=test me" -nodes -new -newkey rsa:3072 -outform der -out - | base64 > test.pem
curl -s -H "Content-Type: application/pkcs10" --data @test.pem \
--key estclient/deadbeaf.key.pem --cert estclient/deadbeaf.cert.pem \
https://demo.openxpki.org/.well-known/est/simpleenroll | base64 -d | openssl pkcs7 -inform der -print_certs

Note: The EST standard states that only the end-entity certificate must be included in the response. To get the chain certificates query the cacerts method of the endpoint:

curl -s https://demo.openxpki.org/.well-known/est/cacerts | base64 -d | openssl pkcs7 -inform der -print_certs

SCEP Server

The communication with your scep clients requires the deployment of a cgi wrapper script with your webserver. The script will parse the HTTP related parts and pass the data to the openxpki daemon and vice versa.

Decommission and Upgrade Notice

With v3.26 the old SCEP wrappers based on a dedicated service layer are no longer supported. You need to remove the service related items from system.server.service, system.crypto.tokenapi and point the /scep alias rules in the apache wrapper to the scepv3.fcgi script. You also need to update the wrapper configurations in the /etc/openxpki/scep folder and the workflow configurations in the realms.

Wrapper Configuration

The default wrapper looks for its config file at /etc/openxpki/scep/default.conf. The config uses plain ini format, a default is deployed by the package:

[global]
socket=/var/openxpki/openxpki.socket
realm=democa
servername=generic

[logger]
# A loglevel of DEBUG MIGHT disclose sensitive user input data
# A loglevel of TRACE WILL dump any communication unfiltered
log_level = INFO

[auth]
stack=_System

# OpenXPKI supports mapping additional URL Parameters to the workflow
# Those must be whitelisted here for security reasons
[PKIOperation]
param = signature
Config Path Expansion

Is supported by the SCEP wrapper, the service name is scep. See the common wrapper documentation (Wrapper Configuration) for details.

Caveats

The scep standard is not exact about the use of HTTP/1.1 features. We saw a lot of clients which where sending plain HTTP/1.0 requests which is not compatible with name based virtual hosting!

Please do NOT use SCEP over HTTPS, SCEP transport is protected on the application layer by default.

SOAP Server

The builtin SOAP Server provides methods to revoke certificates. The service is implemented using a cgi-wrapper script, so there is no need for the webserver to support SOAP, you just need to setup the wrapper script. For apache, just add a ScriptAlias:

ScriptAlias /soap  /usr/lib/cgi-bin/soap.fcgi

Wrapper Configuration

The default wrapper looks for its config file at /etc/openxpki/scep/default.conf. The config uses plain ini format, a default is deployed by the package:

[global]
socket = /var/openxpki/openxpki.socket
modules = OpenXPKI::SOAP::Revoke OpenXPKI::SOAP::Smartcard

[logger]
log_level = WARN

[auth]
stack = _System

[OpenXPKI::SOAP::Revoke]
workflow = certificate_revocation_request_v2
servername = signed-revoke

[OpenXPKI::SOAP::Smartcard]
workflow = sc_revoke
servername = smartcard-revoke

The global/auth parameters are described in the common wrapper documentation (Wrapper Configuration). Config path extension is supported.

The modules key must list the class names of all modules that should be exposed. Most modules expect some extra configuration. Put your parameters into a section with the name of the module, those will be passed to the module when initialized.

Endpoint Configuration

Based on the given servername, a rules file is loaded for the server. You can define the rules for the signer authorization here:

authorized_signer:
    rule1:
        subject: CN=.+:soapclient,.*
    rule2:
        subject: CN=.+:pkiclient,.*

SOAP Methods

The default interface exposes two methods. The reason code is optional in both calls and defaults to “unspecified”. Allowed values are the reason codes as used by openssl.

RevokeCertificateByIssuerSerial

This expects the full DN of the certificate issuer and the serial number of the certificate to revoke. The serial can be either in decimal or hexadecimal format prefixed with ‘0x’:

RevokeCertificateByIssuerSerial(
    'CN=CA ONE,OU=Test CA,DC=OpenXPKI,DC=ORG',
    '0xdb7d5b06600bddcbecff',
    'keyCompromise'
)
RevokeCertificateByIdentifier

Expects the OpenXPKI identifier of the certificate:

RevokeCertificateByIdentifier(
    'TZNrDctI9RV8DT5TGvg81w7F-So',
    'keyCompromise'
)

Both calls return a hash with id and state of the started workflow:

{
  'id' => '145919',
  'state' => 'PENDING',
  'error' => ''
}

If anything goes wrong, you get a verbose error message in error:

{
  'error' => 'parameter missing'
}

Operation

Audit Log

The audit log lists all operations that are relevant for the usage of private key material or important steps (as approvals) that lead to a signature using the CA key.

Categories

The audit log is divided into several categories. The given items are actions logged by the standard configuration but are not exhaustive. The name in brackets is the name of the logger category used by the logger.

CA Key Usage (cakey)
  • certificate issued
  • crl issued
Entity Key Usage (key)
  • key generated
  • key exported
  • key destroyed
Certificate (entity)
  • request received
  • request fully approved
  • issued
  • revoked
Approval (approval)
  • operator approval given via ui
  • automated approval derived from backend checks
ACL (acl)
  • access to workflow
  • access to api
System (system)
  • start/stop of system
  • import/activation of tokens
  • import of certificates
Application
  • Application specific logging

Parameters

Each log message consists of a fixed string describing the event plus a list of normalized parameters which are appended as key/value pairs to the message, so it is easy to search the log for certain or feed it to a log analysis program like logstash.

  • cakey/key: subject key identifier of the used key
  • certid: certificate identifier
  • wfid: id of the workflow
  • action: name of a workflow action or called API method
  • token: alias name of the token/key, e.g., “ca-signer-1”
  • pki_realm: name of the pki realm

Example (line breaks are for verbosity, logfile is one line):

certificate signed|
  cakey=28:B9:6D:51:EC:EB:6D:C9:4A:71:7C:B4:C0:67:F7:E9:C1:BD:63:7A|
  certid=FW2Hq52uTcthhyhrrvTjRub66M0|
  key=D6:14:BB:E2:90:12:F4:FF:64:B4:0F:F3:F6:3A:FD:17:02:C9:06:C8|
  pki_realm=democa

Crypto Token Configuration

Overview

A cypto token is an entity used to do cryptographic operations. OpenXPKI organizes those tokens using groups and generations. A default system has four groups:

  • certsign - represents the Issuing CA
  • datasafe - used internally to encrypt sensitive data
  • scep - the operational certificate of the SCEP server
  • root - the root certificate of the Issuing CA chain

OpenXPKI expects that a token has only a limited lifetime and is substituted by a successor at a certain point in time. This relation is expressed by the generation counter.

Initial Setup

All tokens consist of a private key and a certificate, the certificate must be present in the OpenXPKI internal database and is referenced by the certificate identifier. The private key lives outside the OpenXPKI systems. When using the default config, the system expects the private key as file where the name of the file is constructed from the complete alias name.

Root Certificate

For production systems it is usual to have the Issuing CA under a Root CA and manage the Root CA on a offline system. As OpenXPKI needs the full chain of a certificate, you need to import the root certificate first:

openxpkiadm certificate import --file ca-root-1.crt
Issuing Certificate

After importing the root, or if you do not have a dedicated root, you can now import the issuing certificate:

openxpkiadm certificate import  --file ca-signer-1.crt \
    --realm democa --token certsign

This will import the certificate and also create a so called alias to mark this certificate as issuing token. With the default config, the key file is expected to be at /etc/openxpki/ca/democa/ca-signer-1.pem.

Datasafe Token

The datasafe token is represented by a certificate but is never exposed to the public so it is acceptable to use a self-signed certificate here:

openxpkiadm certificate import  --file vault-1.crt \
    --realm democa --token datasafe

The token is used for encrypting new items only as long as the certificate is valid. Expired tokens are still needed to decrypt existing items so never delete or overwrite them!

Token Rollover

If the lifetime of a token is approaching its end, you can just add a new token using the same commands as above. OpenXPKI will increase the internal generation counter and assign it to the new alias. Just make sure your key file has the correct name! If your token key are protected with a password, make sure that all passwords for all generations are still accessible as long as you need the token - issuing tokens are usually used to sign CRLs even after their active issuing period is over and datasafe tokens are required to access archived keys or other data.

Architecture

Developer

Tests

The OpenXPKI project contains a large and still growing number of tests to ensure code quality and ease refactoring.

Historically tests are categorized into two groups:

  • unit tests in core/server/t/: tests for single classes and limited functionality that don’t need a running server or a complete configuration.
  • QA tests in qatest/: tests that need a running server or a more complete configuration.

Running tests

There are several methods to run all tests:

Using Docker

This method has minimal requirements for your host system.

Prerequisites: Docker

# assuming you are in the projects' root directory:
./tools/docker-test.pl --all

The script builds the Docker image locally which takes a while on first run.

Please note that this will only run tests on a MariaDB database. If you want to test Oracle connectivity, please use the Vagrant method below.

Using Vagrant

This method creates a complete interactive test environment inside a VirtualBox VM (i.e., “Vagrant Box”).

Prerequisites: Virtualbox, Vagrant, Oracle XE 11.2 setup

Once

  1. Download the Oracle XE 11.2 setup for Linux from https://www.oracle.com/technetwork/database/database-technologies/express-edition/downloads/xe-prior-releases-5172097.html and place it in vagrant/develop/assets/oracle/docker/setup/packages/ (You need an Oracle login to do that).

  2. Build the Vagrant box (=VM) once, which takes a long while, and start it.

    # assuming you are in the projects' root directory:
    cd vagrant/develop
    vagrant up
    # have several cups of tea...
    

After code changes

  1. Start the Vagrant box and log in

    # assuming you are in the projects' root directory:
    cd vagrant/develop
    vagrant up && vagrant ssh
    
  2. Refresh the code

    To make sure all dependencies inside the VM are in sync with the files on your host (e.g., after code changes), refresh them inside Vagrant:

    sudo su
    oxi-refresh
    # maybe start with a clean DB: oxi-initdb
    
  3. Run the tests

    docker start mariadb
    docker start oracle
    cd /code-repo
    
    cd core/server
    PERL5LIB=./ prove -r t
    cd ../..
    
    cd qatest
    PERL5LIB=./ prove -r backend/api2 backend/webui client
    cd ..
    
Using a local dev environment

Prerequisites: Database, Linux packages etc.

Once

Set up a running OpenXPKI instance as described in Quickstart guide. Please note that the tests currently use the database that is configured in /etc/openxpki/config.d.

After code changes

  1. Update required Perl according to current Makefile

    # assuming you are in the projects' root directory:
    cpanm Carton
    ./tools/scripts/makefile2cpanfile.pl > cpanfile
    carton install
    
  2. Run the tests

    # assuming you are in the projects' root directory:
    cd core/server
    prove -I ../../local/lib/perl5 -r t
    cd ../..
    
    cd qatest
    prove -I ../local/lib/perl5 -I ../core/server -r backend/api2 backend/webui client
    cd ..
    
Automatically via Travis-CI

Whenever a new commit of the code is pushed onto GitHub, a Travis-CI test run is triggered that runs all of the active tests.

You can find the results at https://travis-ci.org/openxpki/openxpki.

For more details see .travis.yml in the projects’ root directory.

Writing tests

Tests are important and we are glad if you want to contribute a test, e.g., for a bug you have found or a new/untested feature!

OpenXPKI itself is quite complex. That is why there is a bunch of Perl classes that help minimizing the boilerplate code you have to write in each test. They also do some of the tricky setup in the background so you should be able to concentrate on the test logic.

Please have a look at the documentation of OpenXPKI::Test to start and understand how the test class(es) work.

Please note that there are still old tests around which do not use the new test class. They will be migrated over time.

Glossary

CA Rollover
Using multiple CA Certificates with overlapping validity to issue certificates for the same purpose and namespace. CA Rollover allows for continuous and uninterrupted operation of a PKI.
PKI Realm
A PKI realm is a “Logical Certificate Authority” which usually includes one or more [Issuing_CA] that are responsible for issuing certificates within the same name space.

Indices and tables