Java Key Client
- 1 Overview
- 2 Preliminary Setup
- 3 Setup for the Java client
- 4 Working with the Key Client
- 5 References
The Java Key Client framework provides an easy way to perform the CRUD operations with respect to keys and key requests, on the DRM, over the REST framework.
It allows users to perform GET and POST calls for archiving, retrieving and modifying the keys on the DRM.
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 .
The following steps detail how to set up a DS, CA and KRA using the default ports (ports 8443 for https, 8080 for http)
- Provision a Fedora 20 x86_64 machine.
- yum install the packages at the above link.
Create DS instance
setup-ds.pl --silent --\ General.FullMachineName=$HOSTNAME\ General.SuiteSpotUserID=nobody\ General.SuiteSpotGroup=nobody\ slapd.ServerPort=389\ slapd.ServerIdentifier=pki-tomcat\ slapd.Suffix=dc=example,dc=com\ slapd.RootDN="cn=Directory Manager"\ slapd.RootDNPwd=Secret.123
Create CA instance
Download ca.cfg. Execute:
$ pkispawn -v -f ca.cfg -s CA
Create DRM instance
Download kra.cfg. Execute:
$ pkispawn -v -f kra.cfg -s KRA
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
The client uses JSS(NSS in Java) libraries to interact with the DRM. All interactions with the DRM 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.
Some of the functions in the key client require some cryptographic operations like generating a symmetric key, or wrapping a symmetric key with the DRM 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 DRM 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 DRM 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 DRMTest.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 DRM when it is generated or archived. It is uniquely only to this DRM (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 DRM. Both the transWrappedSessionKey and sessionWrappedPassphrase are sent to the DRM, 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 DRM. 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 DRM.
// 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 DRM transport certificate. This session key will be decoded on the DRM, 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.