Soporte al cliente de SCEP

Esta página proporciona ejemplos de código e información necesaria para crear un cliente SCEP. Estos ejemplos de código no se mantienen y podrían no ser compatibles con futuras versiones de BouncyCastle.

Todas las clases que se encuentran en esta página, no en las bibliotecas estándar de Java, son de BouncyCastle o están en EJBCA.

Generar un mensaje PKCSREQ

Una solicitud de certificado PKCSREQ básica es una solicitud de certificado PKCS10 estándar encapsulada en un sobre PKCS7. Para ello, se puede encontrar el siguiente código en las pruebas del protocolo EJBCA:

public byte [] generateCertReq(String dn, String password, String transactionId, X509Certificate ca, Extensions exts,
Certificado X509 final X509Certificate senderCertificate, clave privada 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
Solicitud de certificación PKCS10 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,
clave privada 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;
}

Generar un mensaje GetCRL

public byte [] generateCrlReq(String dn, String transactionId, X509Certificate ca, X509 final X509Certificate senderCertificate,
clave privada 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);
}

Generar un mensaje GetCertInitial

El mensaje GetCertInitial se utiliza al sondear EJBCA en modo RA mientras se espera la emisión de un certificado que requiere la aprobación de un administrador.

public byte [] generateGetCertInitial(String dn, String transactionId, X509Certificate caCertificate, X509 final X509Certificate senderCertificate,
clave privada 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);
}

Análisis de una respuesta SCEP

El siguiente código de muestra de las pruebas de protocolo de EJBCA verifica y extrae el contenido de una respuesta SCEP exitosa

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, ie 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
Lista 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
Colección 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);
}
}
}