jscep is a Java implementation of the SCEP protocol.
If you have a question about the jscep library, please send an email to [email protected].
In order to construct a client, we need two objects:
- a URL
- a Callback Handler
The URL should be obtained from your system administrator. In the case of Microsoft NDES, the URL will look like so:
URL url = new URL("http://[host]/certsrv/mscep_admin/mscep.dll");
In the case of EJBCA, it will look like so:
URL url = new URL("http://[host]/ejbca/publicweb/apply/scep/pkiclient.exe");
jscep doesn't directly support using a proxy to access your SCEP server, as it doesn't really make sense for SCEP. However, if you need to use a proxy, you can use the mechanism provided by ProxySelector, like so:
ProxySelector.setDefault(new ProxySelector() {
@Override
public List<Proxy> select(URI uri) {
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("squid", 3128);
return Collections.singletonList(proxy);
}
@Override
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
// Do nothing
}
});
jscep uses HttpURLConnection under the hood, and offers full support for HTTPS-enabled SCEP servers - although SCEP doesn't require a HTTPS connection.
If your SCEP server requires the use of SSL to establish a connection, you may wish to configure
HttpsURLConnection by using the static
setDefaultHostnameVerifier
and setDefaultSSLSocketFactory
methods. You'll only need to specify a HostnameVerifier
if your SSL server provides a certificate that doesn't match the hostname in the SCEP URL.
By default, HttpsURLConnection
will use the SSLSocketFactory
as specified by JSSE, so there should be no need to configure it directly. However, if you do want to customise the socket factory, you can do this by customising the transport (see below).
For more information, read the JSSE Reference Guide, particularly the section on customization.
If you want to provide your own transport implementation (e.g. using something other than HttpURLConnection) take a look at the TransportFactory interface. Once instantiated, you can inject the custom TransportFactory
using Client.setTransportFactory()
.
If you're looking to customise the default SSLSocketFactory
used by UrlConnectionTransportFactory, you can do so without creating your own custom TransportFactory
. Instead, instantiate UrlConnectionTransportFactory
yourself, and pass in your customised SSLSocketFactory
, like so:
SSLSocketFactory factory = new CustomisedSSLSocketFactory();
Client client = new Client(url, handler);
client.setTransportFactory(new UrlConnectionTransportFactory(factory));
Ernst-Georg Schmid has created a transport which uses HTTP Basic authentication. You can find his repo at ergo70/jscep-basic-auth.
The callback handler is used to verify the CA certificate being sent by the SCEP server is the certificate you expect. With jscep, you can choose to use either the default callback mechanism with a choice of certificate verifiers, or to provide your own callback handler.
The default callback mechanism provides a DefaultCallbackHandler
which delegates verification to a CertificateVerifier
implementation. jscep supports several strategies for verifying a certificate, including pre-provisioned certificates or digests, and an interactive console verifier. The following example shows the steps necessary to configure the console verifier:
CertificateVerifier verifier = new ConsoleCertificateVerifier();
CallbackHandler handler = new DefaultCallbackHandler(verifier);
By default, jscep will request verification before each operation. If you are performing a number of operations against the same SCEP server, you may wish to cache the users response by decorating the certificate verifier, like so:
CertificateVerifier consoleVerifier = new ConsoleCertificateVerifier();
CertificateVerifier verifier = new CachingCertificateVerifier(consoleVerifier);
CallbackHandler handler = new DefaultCallbackHandler(verifier);
If you wish to use your own CallbackHandler
, you must handle the CertificateVerificationCallback
.
To create the client, just combine the two parameters:
Client client = new Client(url, handler);
The client is thread-safe, so you can use to enrol multiple entities in parallel if you're using the same CA.
If your SCEP server supports multiple CAs, your CA administrator must provide a string to identify the issuer to use. Each of the operations supported by jscep accepts an optional profile parameter in the form of a String
.
Because the jscep client is thread-safe, your application can invoke operations against multiple CA profiles without having to construct a new SCEP client.
Note: Microsoft NDES always requires a profile.
In each SCEP message exchange, there are two parties: the requester -- who is enrolling a particular entity into a PKI -- and a SCEP server, which represents the issuing authority, or CA.
For most operations, the SCEP server requires that the requester sign and encrypt its requests. In turn, the server will sign and encrypt its responses. In order for this to occur, both parties must have a certificate and key pair.
If the requester has been issued a certificate by the CA, the requester should use that certificate and its associated key pair. Likewise, if the requester has been issued a certificate by a different CA which is trusted by the current CA, then the requester should use that certificate and key pair. Otherwise -- and this is for the majority of cases -- the requester should generate a self-signed certificate.
Before we can generate a certificate, we must first generate a key pair. The SCEP specification only supports RSA, so that is what we will use. The JCA requires Java implementations to support 1024 and 2048-bit keys.
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024);
KeyPair requesterKeyPair = keyPairGenerator.genKeyPair();
Once you have your key pair, the next step is to generate an X509 Certificate. The JCA doesn't provide a mechanism for building certificates programmatically. However, you can use Bouncy Castle to do it, using either the JcaX509v1CertificateBuilder or the JcaX509v3CertificateBuilder class.
The following example uses JcaX509v3CertificateBuilder
due to its support for X509 extensions. Bouncy Castle provides classes and interfaces to simplify the usage of extensions through the org.bouncycastle.asn1.x509 package.
If you don't require extensions, you can use JcaX509v1CertificateBuilder
, which takes the same arguments as JcaX509v3CertificateBuilder
in its JCA-compatible constructor. In either case, you will need to provide a ContentSigner
, which can bebuilt using JcaContentSignerBuilder.
SCEP supports the following signature algorithms:
MD5withRSA
SHA1withRSA
SHA256withRSA
SHA512withRSA
You can find out the strongest signature algorithm supported by your SCEP server by using the following snippet.
Capabilities caps = client.getCaCapabilities();
String sigAlg = caps.getStrongestSignatureAlgorithm();
Note: if you're using a self-signed certificate, your certificate subject X500 name must be the same as the subject in your certificate-signing request.
// Mandatory
X500Principal requesterIssuer = new X500Principal("CN=jscep.org, L=Cardiff, ST=Wales, C=UK");
BigInteger serial = BigInteger.ONE;
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DATE, -1); // yesterday
Date notBefore = calendar.getTime();
calendar.add(Calendar.DATE, +2); // tomorrow
Date notAfter = calendar.getTime();
X500Principal requesterSubject = new X500Principal("CN=jscep.org, L=Cardiff, ST=Wales, C=UK"); // doesn't need to be the same as issuer
PublicKey requesterPubKey = requesterKeyPair.getPublic(); // from generated key pair
JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(requesterIssuer, serial, notBefore, notAfter, requesterSubject, requesterPubKey);
// Optional extensions
certBuilder.addExtension(X509Extension.keyUsage, false, new KeyUsage(KeyUsage.digitalSignature));
// Signing
PrivateKey requesterPrivKey = requesterKeyPair.getPrivate(); // from generated key pair
JcaContentSignerBuilder certSignerBuilder = new JcaContentSignerBuilder(sigAlg); // from above
ContentSigner certSigner = signerBuilder.build(requesterPrivKey);
X509CertificateHolder certHolder = certBuilder.build(certSigner);
You can extract a JCA-compatible certificate by using the JcaX509CertificateConverter:
JcaX509CertificateConverter converter = new JcaX509CertificateConverter();
X509Certificate requesterCert = converter.getCertificate(certHolder);
Congratulations! You now have everything you need to invoke operations against your SCEP server.
Certificate enrollment is the primary use-case for using the SCEP protocol.
When enrolling an entity into a PKI, you should generate a new key pair to represent the entity, as shown in the following snippet. There is no reason not to reuse the KeyPairGenerator
from the earlier steps, but we'll create another here for simplicity.
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024);
KeyPair entityKeyPair = keyPairGenerator.genKeyPair();
We'll name this key pair entityKeyPair
to distinguish it from the key pair used to represent the SCEP client, which is named requesterKeyPair
. After the key pair has been created, we need to start creating the signing request to send to the CA. Since the JCA does not support the creation of CSRs, we'll use Bouncy Castle again:
X500Principal entitySubject = requesterSubject; // use the same subject as the self-signed certificate
PublicKey entityPubKey = entityKeyPair.getPublic();
PKCS10CertificationRequestBuilder csrBuilder = new JcaPKCS10CertificationRequestBuilder(entitySubject, entityPubKey);
We can now use the PKCS10CertificationRequestBuilder
to add attributes. Depending on your SCEP server, you may need to provide additional extensions, but in most cases, you'll add a PKCS#9 challengePassword
:
DERPrintableString password = new DERPrintableString("password");
csrBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_challengePassword, password);
If you're renewing a certificate, you should still send an empty password, as per the following snippet, but the SCEP server must validate the request against the requester certificate, requesterCert
.
DERPrintableString password = new DERPrintableString("");
csrBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_challengePassword, password);
If you wish to add extensions to your CSR, add an extensionRequest OID. BC provides an ExtensionsGenerator
to simplify common use cases:
ExtensionsGenerator extGen = new ExtensionsGenerator();
extGen.addExtension(Extension.extendedKeyUsage, false, new ExtendedKeyUsage(KeyPurposeId.id_kp_eapOverLAN));
csrBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extGen.generate());
In some cases, BC won't provide a specific object, so you'll have to build it yourself:
ASN1EncodableVector otherName = new ASN1EncodableVector();
otherName.add(new DERObjectIdentifier("1.3.6.1.4.1.311.20.2.3"));
otherName.add(new DERTaggedObject(true, 0, new DERUTF8String( "[email protected]")));
ASN1Object genName = new DERTaggedObject(false, 0, new DERSequence(otherName));
ASN1EncodableVector genNames = new ASN1EncodableVector();
genNames.add(genName);
ExtensionsGenerator extGen = new ExtensionsGenerator();
extGen.addExtension(Extension.subjectAlternativeName, true, new DERSequence(genNames));
When you've finished adding your attributes, you must then sign your CSR with your entity's private key.
PrivateKey entityPrivKey = entityKeyPair.getPrivate();
JcaContentSignerBuilder csrSignerBuilder = new JcaContentSignerBuilder("SHA1withRSA");
ContentSigner csrSigner = csrSignerBuilder.build(entityPrivKey);
PKCS10CertificationRequest csr = csrBuilder.build(csrSigner);
You now have everthing you need to enrol. The next line in your application will typically be to send the CSR to the SCEP server, and to assign the response.
EnrollmentResponse res = client.enrol(requesterCert, requesterPrivKey, csr);
Understanding the server response is critical for knowing what to do next.
The EnrollmentResponse
returned by Client.enrol()
and Client.poll()
should be inspected by your application to determine what to do next. EnrollmentResponse
contains three methods which can be used to determine the state of the response:
isSuccess()
isPending()
isFailure()
If isSuccess()
returns true
, your application should call getCertStore()
to retrieve the enrolled certificates. For a lot of applications, this will be the last interaction you have with the jscep client.
If isFailure()
returns true
, your application should call getFailInfo()
to determine the reason for failure. Applications should treat this as a permanent failure. Unfortunately, the SCEP protocol doesn't provide a lot of detail for failure reasons, so it is non-trivial to make your application resilient to SCEP failure.
The last method, isPending()
, is guaranteed to be true
if the other two methods return false
. For pending response, your application should call getTransactionId()
, and use the returned TransactionId
when invoking Client.poll()
, as detailed below.
Your application may use a number of different approaches for sending a poll request to the server, and jscep does not attempt to second-guess how your application will want to approach this situation. However, it should be noted that all of the classes used as arguments to poll
implement Serializable
and are immutable, so can safely be used in different threads and even in different JVMs.
Applications are strongly recommended not to pass the requester PrivateKey
around in the clear. The JCA provides the KeyStore
class for securely storing keys, and can store the requester certificate and pair like so:
KeyStore store = KeyStore.getInstance("JKS");
store.load(null, null);
store.setKeyEntry("requester", requesterPrivKey, "secret".toCharArray(), requesterCert);
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
store.store(bOut, "secret".toCharArray());
Alternatively, applications can use a SealedObject
to simplify serialization, but this is arguably more complicated.
If your application has previously received a pending response, your application should poll the SCEP server to determine the current state of the enrollment. The poll()
method returns the same type as the enrol()
method, so applications should follow the same steps to determine the current state of the enrollment.
EnrollmentResponse res = client.poll(requesterCert, requesterPrivKey, subject, transId);
Since issuing a certificate may involve a lengthy manual process, your application may have to make numerous polling requests.
If you need to retrieve a CRL for a particular certificate.
X509CRL crl = client.getRevocationList(cert, keyPair.getPrivate(), issuer, serial);
If you need to access a certificate that was previously issued, you need only pass the serial number of the certificate:
CertStore store = client.getCertificate(cert, keyPair.getPrivate(), serial);
The capabilities of the SCEP server are used extensively by internal jscep operations, for determining the cipher to use for key wrapping in the pkcsPkiEnvelope
structure, and for the signature to use for signing the pkiMessage
structure.
By default, jscep will invoke this operation to determine which algorithms to use when constructing secure message objects.
Capabilities capabilities = client.getCaCapabilities();
- Digest Algorithms:
- MD5
- SHA-1
- SHA-256
- SHA-512
- Ciphers:
- DES
- Triple DES ( default )
- AES-128
- AES-192
- AES-256
- Use of HTTP POST (See: http://tools.ietf.org/html/draft-nourse-scep-23#appendix-C)
Note: AES-192 and AES-256 needs unrestricted policy JARs
CertStore store = client.getRolloverCertificate();
See: http://tools.ietf.org/html/draft-nourse-scep-23#appendix-E
Retrieving the CA and RA certificates from the SCEP server is an important operation.
CertStore store = client.getCaCertificate();
To enable logging in jscep, you need to provide an SLF4J binding (e.g. log4j, jcl) to your classpath, then provide a configuration for your binding. For example, the jscep project uses log4j for logging during the build process by using the following dependencies in the pom.xml
:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
<scope>test</scope>
</dependency>
and using this configration file: https://github.com/jscep/jscep/blob/master/src/test/resources/log4j.properties
Thanks to Ryan Schipper and Danny deSousa for contributions to this manual.