Skip to content

PKI Key Client in Java

Endi S. Dewata edited this page Jul 17, 2021 · 3 revisions

Overview

The Java Key Client framework provides an easy way to perform the CRUD operations with respect to keys and key requests, on the KRA, over the REST framework.

It allows users to perform GET and POST calls for archiving, retrieving and modifying the keys on the KRA.

A build for x86_64 architectures for Fedora 20 can be found here: http://vakwetu.fedorapeople.org/10.2.0/.

On installing the rpms, the jar files can be found at /usr/share/java/pki.

Prerequisites

Install CA and KRA as described in the Installation Guide.

Setup for the Java client

Setting up Buildpath/Classpath

Following are the jars to be included in the buildpath/classpath of your code to use the client framework.

### Jars provided by Dogtag rpms ###
/usr/share/java/pki/pki-certsrv.jar
/usr/share/java/pki/pki-tools.jar
/usr/share/java/pki/pki-nsutil.jar
/usr/share/java/pki/pki-cmsutil.jar

### External Jar files ###
/usr/share/java/apache-commons-cli.jar
/usr/share/java/apache-commons-logging.jar
/usr/share/java/jss/jss4.jar
/usr/share/java/jaxb-api.jar
/usr/share/java/resteasy/jaxrs-api.jar
/usr/share/java/httpcomponents/httpclient.jar
/usr/share/java/httpcomponents/httpcore.jar
/usr/share/java/resteasy/resteasy-client.jar
/usr/share/java/resteasy/resteasy-jackson-provider.jar
/usr/share/java/resteasy/resteasy-jaxrs.jar
/usr/share/java/commons-httpclient.jar
/usr/share/java/commons-io.jar
/usr/share/java/commons-codec.jar
/usr/share/java/jackson/jackson-core-asl.jar
/usr/share/java/jackson/jackson-jaxrs.jar
/usr/share/java/jackson/jackson-mapper-asl.jar
/usr/share/java/jackson/jackson-mrbean.jar
/usr/share/java/jackson/jackson-smile.jar
/usr/share/java/jackson/jackson-xc.jar
/usr/share/java/jackson-jaxrs-providers/jackson-jaxrs-base.jar
/usr/share/java/jackson-jaxrs-providers/jackson-jaxrs-json-provider.jar
/usr/share/java/idm-console-base.jar
/usr/share/java/idm-console-mcc.jar
/usr/share/java/idm-console-nmclf.jar
/usr/share/java/jakarta-commons-httpclient.jar
/usr/share/java/ldapjdk.jar
/usr/share/java/jackson-annotations.jar
/usr/share/java/jackson-core.jar
/usr/share/java/jackson-databind.jar
/usr/share/java/jackson-module-jaxb-annotations.jar
/usr/share/java/resteasy/resteasy-jaxb-provider.jar
/usr/share/java/resteasy/resteasy-atom-provider.jar
/usr/share/java/apache-commons-lang.jar

Authentication

The client uses JSS (NSS in Java) libraries to interact with the KRA. All interactions with the KRA require client certificate authentication by a trusted agent.An admin user who is also a trusted agent is created as part of the installation process. The PKCS12 file containing the cert/private key for this admin user can be found at ~/.dogtag/pki-tomcat/ca_admin_cert.p12. Since you can only install the subsystems as a root user on your system, you should also provide access to the file for all other users. You can use commands like certutil and pk12util, provided by nss-tools package, to create an NSS database, import the p12 file into the database and use it for authentication.

Following are the steps to setup an NSS database and import the cert stored in the p12 file:

root$ cp ~/.dogtag/pki-tomcat/ca_admin_cert.p12 /home/pki-user
root$ chown pki-user:pki-user /home/pki-user/ca_admin_cert.p12

pki-user$ cd /tmp; mkdir nssdb
pki-user$ certutil -N -d nssdb/
** Enter the password for the cert database **
pki-user$ pk12util -i ~/ca_admin_cert.p12 -d nssdb/
** Enter the passwords for the NSS database and the p12 file(default: Secret.123) **

You can view the imported cert by executing:
pki-user$ certutil -L -d nssdb/

If you have the CA and KRA already installed, you can test authentication using CLI command (which uses the client internally)
pki-user$ pki -d /tmp/nssdb -c <NSS DB Password> -n "<Certificate nickname>" key-find

This database can now be used for authentication.

Cryptography

Some of the functions in the key client require some cryptographic operations like generating a symmetric key, or wrapping a symmetric key with the KRA transport key. There are three possible options here. The method you select will depend on your particular environment.

Option 1: Use NSS Crypto locally.

In this option, an NSS database needs to be set up locally, and used by the Java client for crypto operations. Since you will already have an NSS database for authentication, this approach takes the least effort to perform all the required crypto operations. The Java class that implements these operations is NSSCryptoProvider located in /usr/share/java/certsrv.jar. The constructor of NSSCryptoProvider will initialize the NSS database at the location specified in the ClientConfig object. Here is how we pass on the NSSCryptoProvider to the KeyClient.

ClientConfig config = new ClientConfig();
config.setServerURI(protocol + "://" + host + ":" + port + "/kra");
config.setCertNickname(clientCertNickname);
config.setCertDatabase(NSS_DB_DIR);
config.setCertPassword(NSS_DB_PWD);
NSSCryptoProvider nss = new NSSCryptoProvider(config);

KRAClient client = new KRAClient(new PKIClient(config, nss));
SystemCertClient systemCertClient = (SystemCertClient) client.getClient("systemcert");
KeyClient keyClient = (KeyClient) client.getClient("key");

// Get transport certificate from KRA and pass it on to the KeyClient
transportCert = systemCertClient.getTransportCert().getEncoded();
transportCert = transportCert.substring(PKIService.HEADER.length(),
                transportCert.indexOf(PKIService.TRAILER));
keyClient.setTransportCert(transportCert);

So you first create a ClientConfig instance storing the details of the server URI, and the NSS database. Create an NSSCryptoProvider instance using the ClientConfig object and pass it on to the KRAClient as part of the PKIClient instance. You can access the KeyClient from the KRAClient instance. The PKIClient which has all the configuration details will take care of the authenticating using the NSS database.

Option 2: Use something else (OpenSSL?) crypto locally

In this option, the crypto operations (generating keys/ wrapping/ unwrapping) would still be done locally, but not using NSS for the cryptographic library. The NSSCryptoProvider which does the crypto operations is an implementation of an abstract base class CryptoProvider. All the functions declared in the CryptoProvider class are used by the key client to perform the crypto operations.

To use something other than NSS then, you would need to subclass CryptoProvider and implement the abstract methods. The setup code would then be similar to the code shown above - except that your subclass would be passed into the constructor for PKIClient instead of the NSSCryptoUtil class.

We plan to write an OpenSSLCryptoUtil at some point soon as well.

Option 3: Handling the crypto operations outside the Key client

In this option, all cryptographic operations are done outside of the Java key client and the relevant encrypted values are passed in when key client calls are made. This is the case when the symmetric keys and wrappings are being done on a separate application, and this application does not interact with the KRA directly.

Working with the Key Client

As shown in the code samples above, the end result is a KeyClient object which is defined in KeyClient.java. Furthermore, in the source code in KRATest.java, there are examples of the invocation of the functions. You should look at that class to see all the relevant methods and more detailed description of each method. We will describe the most common use cases below.

There are a few parameters that are worth mentioning though:

  • clientKeyId: this is a label that is provided by the caller for the stored secret. Secrets can be either active or inactive, but there should only be one active secret per client key id. Attempting to archive or generate another key with the same client_key_id will fail, if the existing key is active. It is possible to modify the status of an existing key using the modifyStatus() call. Care should be exercised though - it is possible to modify more than key’s status to "active". It is also the responsibility of the caller to maintain uniqueness of a client_key_id. If this parameter is in the method definition, then it usually required. Note that there is currently a restriction that the client_key_id should not include "/" characters,

  • keyId: this is a unique identifier assigned to the secret by the KRA when it is generated or archived. It is uniquely only to this KRA (and its clones).

  • A note about exceptions: In general, if invalid parameters are detected on the client side, an IllegalArgumentException is thrown. Server exceptions are thrown as PKIException objects.

Generating and archiving a symmetric key

This function takes in some key parameters and returns the key_id for the generated (and archived) key. In future, it will also be able to return the generated key at the same time. We will implement that functionality soon.

String clientKeyId = "Symmetric Key #1234f " + Calendar.getInstance().getTime().toString();
List<String> usages = new ArrayList<String>();
usages.add(SymKeyGenerationRequest.DECRYPT_USAGE);
usages.add(SymKeyGenerationRequest.ENCRYPT_USAGE);
KeyRequestResponse genKeyResponse = keyClient.generateSymmetricKey(clientKeyId,
                                    KeyRequestResource.AES_ALGORITHM,
                                    128, usages, null);
KeyId keyId = genKeyResponse.getKeyId();

Archiving a secret

Different methods, depending on the type of the secret and how it is passed, are provided by the Java client framework.

String passphrase = "Secret.123";
String clientKeyId = "UUID: 123-45-6789 RKEK " + Calendar.getInstance().getTime().toString();

KeyRequestResponse requestResponse = keyClient.archivePassphrase(clientKeyId, passphrase);
// Print the request information
printRequestInfo(requestResponse.getRequestInfo());
keyId = requestResponse.getKeyId();

The above code archives a secret of passphrase type. The secret is passed directly to the client without any encryption i.e the crypto operations are done locally. A session key is generated and is used to wrap the secret. The session key is then wrapped using the public key in the transport cert of the KRA. Both the transWrappedSessionKey and sessionWrappedPassphrase are sent to the KRA, which does the decryption and archives the secret.

A symmetric key can be archived using the archiveSymmetricKey method.

For the case where encryption is done externally, the method archiveEncryptedData can be used, to archive the secret.

A secret can also be archived by creating a PKIArchiveOptions object using the secret (passphrase/symmetric key) and transport cert of the KRA. This object can be used to archive the secret.

String clientKeyId = "UUID: 123-45-6789 VEK " + Calendar.getInstance().getTime().toString();

SymmetricKey secret = CryptoUtil.generateKey(token, KeyGenAlgorithm.DES3);
byte[] iv = CryptoUtil.getNonceData(8);
byte[] encoded = nss.createPKIArchiveOptions(transportCert, secret, null, KeyGenAlgorithm.DES3, iv);
KeyRequestResponse info = keyClient.archivePKIOptions(clientKeyId, KeyRequestResource.SYMMETRIC_KEY_TYPE,
                                                      KeyRequestResource.DES3_ALGORITHM, 0, encoded);
printRequestInfo(info.getRequestInfo());

Retrieving a secret

There are different methods depending on whether the client is doing crypto operations locally or whether the crypto operations are being performed outside of the Dogtag Python client.

This is the case where the crypto operations are done locally (as in section 1 or 2 in Cryptography section).

// get active key for a particular client ID
KeyId keyInfo = keyClient.getActiveKeyInfo(clientKeyId);
KeyId keyId2 = keyInfo.getKeyId();

Key keyData = keyClient.retrieveKey(keyId);

keyData will contain information about the secret (algorithm etc.) along with the secret as the attribute "data" since there is no transport key wrapped session key passed in the retrieveKey call.

The following code snippet is an example of encryption done externally, i.e. an application or an intermediate server which uses the client API to talk to the KRA.

// get active key for a particular client ID
KeyId keyInfo = keyClient.getActiveKeyInfo(clientKeyId);
KeyId keyId2 = keyInfo.getKeyId();
SymmetricKey sessionKey = crypto.generateSessionKey();
byte[] transWrappedSessionKey = crypto.wrapSessionKeyWithTransportCert(sessionKey, transportCert);

Key keyData = keyClient.retrieveKey(keyId, transWrappedSessionKey);

// executed on the client where session key was generated
byte[] encryptedKey = keyData.getEncryptedData();

byte[] unwrappedKey = crypto.unwrapWithSessionKey(encryptedKey, recoveryKey, KeyRequestResource.DES3
                                                  keyData.getNonceData());
// For a passphrase, secret will be the passphrase stored.
// For a symetric key, it is the encoded key string.
String secret = new String(unwrappedKey, "UTF-8");

transWrappedSessionKey is a 168 bit 3DES symmetric key used as a session key, that has been wrapped by the public key in the KRA transport certificate. This session key will be decoded on the KRA, and will be used to wrap the secret. The encrypted secret will be returned in the Key object.

The code sample above shows how to generate a transWrappedSessionKey using NSSCryptoUtil(crypto) to give you an understanding of how the transWrappedSessionKey is created and used to retrieve a key. This is what is done in the client internally, when the method retrieveKey(KeyId keyId) is called. In addition to retrieving the key data wrapped in a symmetric key, it can also be retrieved wrapped in a pass-phrase. The methods retrieveKeyByPassphrase in the KeyClient class provides this feature.

Clone this wiki locally