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 attributechallpwdattr.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 dataCMSEnvelopedData 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 dataCMSSignedData 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 messageCMSSignedData s = new CMSSignedData(retMsg); // The signer, ie the CA, check it's the right CASignerInformationStore 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 algassertEquals(signerInfo.getDigestAlgOID(), digestOid);SignerId sinfo = signerInfo.getSID(); // Check that the signer is the expected CAassertEquals(CertTools.stringToBCDNString(caCertificate.getIssuerDN().getName()), CertTools.stringToBCDNString(sinfo.getIssuer().toString())); // Verify the signatureJcaDigestCalculatorProviderBuilder 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 attributesAttributeTable tab = signerInfo.getSignedAttributes(); // --Fail infoAttribute attr = tab.get( new ASN1ObjectIdentifier(ScepRequestMessage.id_failInfo)); // No failInfo on this success messageassertNull(attr); // --Message typeattr = 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 statusattr = 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()); // --SenderNonceattr = 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 charsassertEquals( "Expected nonce length was 16." , 16 , octstr.getOctets().length); // --Recipient Nonceattr = 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 nonceString recipientNonce = new String(Base64.encode(octstr.getOctets()));assertEquals( "Incorrect nonce was received back." , _senderNonce, recipientNonce); // --Transaction IDattr = 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 sentassertEquals(_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 dataCMSSignedData sd = new CMSSignedData(decBytes); // Get certificates from the signed datacertstore = 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 CRLassertEquals(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 certificateString subjectdn = CertTools.getSubjectDN(retcert); if (CertTools.stringToBCDNString(userDN).equals(subjectdn)) { // issued certificateassertEquals(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 certificateassertEquals(CertTools.getSubjectDN(caCertificate), CertTools.getSubjectDN(retcert));gotcacert = true ;}}assertTrue(verified); if (noca) {assertFalse(gotcacert);} else {assertTrue(gotcacert);}}}