SCEP Client Support
This page provides code samples and information required when creating a SCEP client. These code samples are not maintained, and may not be in accordance with future versions of BouncyCastle.
All classes found in this page, not in the standard Java libraries, are either from BouncyCastle or in EJBCA.
Generating a PKCSREQ Message
A basic PKCSREQ (certificate request) is a standard PKCS10 certificate request wrapped in a PKCS7 envelope. To do so, the following code can be found in the EJBCA protocol tests:
public
byte
[] generateCertReq(String dn, String password, String transactionId, X509Certificate ca, Extensions exts,
final
X509Certificate senderCertificate,
final
PrivateKey signatureKey, ASN1ObjectIdentifier encryptionAlg)
throws
OperatorCreationException, CertificateException,
IOException, CMSException {
// Generate keys
// Create challenge password attribute for PKCS10
// Attributes { ATTRIBUTE:IOSet } ::= SET OF Attribute{{ IOSet }}
//
// Attribute { ATTRIBUTE:IOSet } ::= SEQUENCE {
// type ATTRIBUTE.&id({IOSet}),
// values SET SIZE(1..MAX) OF ATTRIBUTE.&Type({IOSet}{\@type})
// }
ASN1EncodableVector challpwdattr =
new
ASN1EncodableVector();
// Challenge password attribute
challpwdattr.add(PKCSObjectIdentifiers.pkcs_9_at_challengePassword);
ASN1EncodableVector pwdvalues =
new
ASN1EncodableVector();
pwdvalues.add(
new
DERUTF8String(password));
challpwdattr.add(
new
DERSet(pwdvalues));
ASN1EncodableVector extensionattr =
new
ASN1EncodableVector();
extensionattr.add(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest);
extensionattr.add(
new
DERSet(exts));
// Complete the Attribute section of the request, the set (Attributes) contains two sequences (Attribute)
ASN1EncodableVector v =
new
ASN1EncodableVector();
v.add(
new
DERSequence(challpwdattr));
v.add(
new
DERSequence(extensionattr));
DERSet attributes =
new
DERSet(v);
// Create PKCS#10 certificate request
final
PKCS10CertificationRequest p10request = CertTools.genPKCS10CertificationRequest(
"SHA256WithRSA"
,
CertTools.stringToBcX500Name(reqdn), keys.getPublic(), attributes, keys.getPrivate(),
null
);
// wrap message in pkcs#7
return
wrap(p10request.getEncoded(), Integer.toString(ScepRequestMessage.SCEP_TYPE_PKCSREQ), transactionId, senderCertificate, signatureKey, encryptionAlg);
}
private
byte
[] wrap(
byte
[] envBytes, String messageType, String transactionId,
final
X509Certificate senderCertificate,
final
PrivateKey signatureKey, ASN1ObjectIdentifier encryptionAlg)
throws
CertificateEncodingException, CMSException, IOException {
// Create inner enveloped data
CMSEnvelopedData ed = envelope(
new
CMSProcessableByteArray(envBytes), encryptionAlg);
log.debug(
"Enveloped data is "
+ ed.getEncoded().length +
" bytes long"
);
CMSTypedData msg =
new
CMSProcessableByteArray(ed.getEncoded());
// Create the outer signed data
CMSSignedData s = sign(msg, messageType, transactionId, senderCertificate, signatureKey);
byte
[] ret = s.getEncoded();
return
ret;
}
Generating a GetCRL Message
public
byte
[] generateCrlReq(String dn, String transactionId, X509Certificate ca,
final
X509Certificate senderCertificate,
final
PrivateKey signatureKey, ASN1ObjectIdentifier encryptionAlg)
throws
CertificateEncodingException, CMSException, IOException {
X500Name name = CertTools.stringToBcX500Name(cacert.getIssuerDN().getName());
IssuerAndSerialNumber ias =
new
IssuerAndSerialNumber(name, cacert.getSerialNumber());
// wrap message in pkcs#7
return
wrap(ias.getEncoded(), Integer.toString(ScepRequestMessage.SCEP_TYPE_GETCRL), transactionId, senderCertificate, signatureKey, encryptionAlg);
}
Generating a GetCertInitial Message
The GetCertInitial message is used when polling EJBCA in RA mode while waiting for issuance of a certificate requiring approval from an administrator.
public
byte
[] generateGetCertInitial(String dn, String transactionId, X509Certificate caCertificate,
final
X509Certificate senderCertificate,
final
PrivateKey signatureKey, ASN1ObjectIdentifier encryptionAlg)
throws
CertificateEncodingException, CMSException, IOException {
this
.cacert = caCertificate;
this
.reqdn = dn;
// pkcsGetCertInitial issuerAndSubject ::= {
// issuer "the certificate authority issuer name"
// subject "the requester subject name as given in PKCS#10"
// }
ASN1EncodableVector vec =
new
ASN1EncodableVector();
vec.add(CertTools.stringToBcX500Name(caCertificate.getIssuerDN().getName()));
vec.add(CertTools.stringToBcX500Name(dn));
DERSequence seq =
new
DERSequence(vec);
// wrap message in pkcs#7
return
wrap(seq.getEncoded(), Integer.toString(ScepRequestMessage.SCEP_TYPE_GETCERTINITIAL), transactionId, senderCertificate, signatureKey, encryptionAlg);
}
Parsing a SCEP Response
The following sample code from EJBCA's protocol tests verifies and extracts the contents of a successful SCEP Response
private
void
checkSuccesfulScepResponse(
byte
[] retMsg, X509Certificate caCertificate, String userDN, String _senderNonce, String _transId,
boolean
crlRep,
String digestOid,
boolean
noca, KeyPair keyPair)
throws
CMSException, NoSuchProviderException, NoSuchAlgorithmException, CertStoreException, InvalidKeyException, CertificateException,
SignatureException, CRLException, OperatorCreationException {
// Parse response message
CMSSignedData s =
new
CMSSignedData(retMsg);
// The signer, i.e. the CA, check it's the right CA
SignerInformationStore signers = s.getSignerInfos();
Collection<SignerInformation> col = signers.getSigners();
assertTrue(col.size() >
0
);
Iterator<SignerInformation> iter = col.iterator();
SignerInformation signerInfo = iter.next();
// Check that the message is signed with the correct digest alg
assertEquals(signerInfo.getDigestAlgOID(), digestOid);
SignerId sinfo = signerInfo.getSID();
// Check that the signer is the expected CA
assertEquals(CertTools.stringToBCDNString(caCertificate.getIssuerDN().getName()), CertTools.stringToBCDNString(sinfo.getIssuer().toString()));
// Verify the signature
JcaDigestCalculatorProviderBuilder calculatorProviderBuilder =
new
JcaDigestCalculatorProviderBuilder().setProvider(BouncyCastleProvider.PROVIDER_NAME);
JcaSignerInfoVerifierBuilder jcaSignerInfoVerifierBuilder =
new
JcaSignerInfoVerifierBuilder(calculatorProviderBuilder.build()).setProvider(BouncyCastleProvider.PROVIDER_NAME);
assertTrue(
"Response was not correctly signed by CA."
, signerInfo.verify(jcaSignerInfoVerifierBuilder.build(caCertificate.getPublicKey())));
// Get authenticated attributes
AttributeTable tab = signerInfo.getSignedAttributes();
// --Fail info
Attribute attr = tab.get(
new
ASN1ObjectIdentifier(ScepRequestMessage.id_failInfo));
// No failInfo on this success message
assertNull(attr);
// --Message type
attr = tab.get(
new
ASN1ObjectIdentifier(ScepRequestMessage.id_messageType));
assertNotNull(attr);
ASN1Set values = attr.getAttrValues();
assertEquals(values.size(),
1
);
ASN1String str = DERPrintableString.getInstance((values.getObjectAt(
0
)));
String messageType = str.getString();
assertEquals(
"3"
, messageType);
// --Success status
attr = tab.get(
new
ASN1ObjectIdentifier(ScepRequestMessage.id_pkiStatus));
assertNotNull(attr);
values = attr.getAttrValues();
assertEquals(values.size(),
1
);
str = DERPrintableString.getInstance((values.getObjectAt(
0
)));
assertEquals(
"Response status was not as expected."
, ResponseStatus.SUCCESS.getStringValue(), str.getString());
// --SenderNonce
attr = tab.get(
new
ASN1ObjectIdentifier(ScepRequestMessage.id_senderNonce));
assertNotNull(attr);
values = attr.getAttrValues();
assertEquals(values.size(),
1
);
ASN1OctetString octstr = ASN1OctetString.getInstance(values.getObjectAt(
0
));
// SenderNonce is something the server came up with, but it should be 16 chars
assertEquals(
"Expected nonce length was 16."
,
16
, octstr.getOctets().length);
// --Recipient Nonce
attr = tab.get(
new
ASN1ObjectIdentifier(ScepRequestMessage.id_recipientNonce));
assertNotNull(attr);
values = attr.getAttrValues();
assertEquals(values.size(),
1
);
octstr = ASN1OctetString.getInstance(values.getObjectAt(
0
));
// recipient nonce should be the same as we sent away as sender nonce
String recipientNonce =
new
String(Base64.encode(octstr.getOctets()));
assertEquals(
"Incorrect nonce was received back."
, _senderNonce, recipientNonce);
// --Transaction ID
attr = tab.get(
new
ASN1ObjectIdentifier(ScepRequestMessage.id_transId));
assertNotNull(attr);
values = attr.getAttrValues();
assertEquals(values.size(),
1
);
str = DERPrintableString.getInstance((values.getObjectAt(
0
)));
// transid should be the same as the one we sent
assertEquals(_transId, str.getString());
// First we extract the encrypted data from the CMS enveloped data contained within the CMS signed data
final
CMSProcessable sp = s.getSignedContent();
final
byte
[] content = (
byte
[]) sp.getContent();
final
CMSEnvelopedData ed =
new
CMSEnvelopedData(content);
final
RecipientInformationStore recipients = ed.getRecipientInfos();
Store<X509CertificateHolder> certstore;
Collection<RecipientInformation> c = recipients.getRecipients();
assertEquals(c.size(),
1
);
Iterator<RecipientInformation> it = c.iterator();
byte
[] decBytes =
null
;
RecipientInformation recipient = it.next();
JceKeyTransEnvelopedRecipient rec =
new
JceKeyTransEnvelopedRecipient(keyPair.getPrivate());
rec.setProvider(BouncyCastleProvider.PROVIDER_NAME);
rec.setContentProvider(BouncyCastleProvider.PROVIDER_NAME);
// Option we must set to prevent Java PKCS#11 provider to try to make the symmetric decryption in the HSM,
// even though we set content provider to BC. Symm decryption in HSM varies between different HSMs and at least for this case is known
// to not work in SafeNet Luna (JDK behavior changed in JDK 7_75 where they introduced imho a buggy behavior)
rec.setMustProduceEncodableUnwrappedKey(
true
);
decBytes = recipient.getContent(rec);
// This is yet another CMS signed data
CMSSignedData sd =
new
CMSSignedData(decBytes);
// Get certificates from the signed data
certstore = sd.getCertificates();
assertNotNull(certstore);
if
(crlRep) {
// We got a reply with a requested CRL
final
List<X509CRLHolder> crls = (List<X509CRLHolder>) sd.getCRLs().getMatches(
null
);
assertEquals(crls.size(),
1
);
// CRL is first (and only)
final
X509CRL retCrl =
new
JcaX509CRLConverter().getCRL(crls.get(
0
));
log.info(
"Got CRL with DN: "
+ retCrl.getIssuerDN().getName());
// check the returned CRL
assertEquals(caCertificate.getSubjectDN().getName(), retCrl.getIssuerDN().getName());
retCrl.verify(caCertificate.getPublicKey());
}
else
{
// We got a reply with a requested certificate
final
Collection<X509CertificateHolder> certs = certstore.getMatches(
null
);
// EJBCA returns the issued cert and the CA cert (cisco vpn client requires that the ca cert is included)
if
(noca) {
assertEquals(certs.size(),
1
);
}
else
{
assertEquals(certs.size(),
2
);
}
// Issued certificate must be first
boolean
verified =
false
;
boolean
gotcacert =
false
;
for
(X509CertificateHolder x509CertificateHolder : certs) {
X509Certificate retcert =
new
JcaX509CertificateConverter().getCertificate(x509CertificateHolder);
log.info(
"Got cert with DN: "
+ retcert.getSubjectDN().getName());
// check the returned certificate
String subjectdn = CertTools.getSubjectDN(retcert);
if
(CertTools.stringToBCDNString(userDN).equals(subjectdn)) {
// issued certificate
assertEquals(CertTools.stringToBCDNString(userDN), subjectdn);
assertEquals(CertTools.getSubjectDN(caCertificate), CertTools.getIssuerDN(retcert));
retcert.verify(caCertificate.getPublicKey());
assertTrue(checkKeys(keyPair.getPrivate(), retcert.getPublicKey()));
verified =
true
;
}
else
{
// ca certificate
assertEquals(CertTools.getSubjectDN(caCertificate), CertTools.getSubjectDN(retcert));
gotcacert =
true
;
}
}
assertTrue(verified);
if
(noca) {
assertFalse(gotcacert);
}
else
{
assertTrue(gotcacert);
}
}
}