Java tutorial
/** * The MIT License (MIT) * * Copyright (c) <2013> <Cryptable> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ package org.cryptable.pki.communication; import org.bouncycastle.asn1.*; import org.bouncycastle.asn1.cmp.*; import org.bouncycastle.asn1.crmf.*; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.*; import org.bouncycastle.asn1.x509.Extension; import org.bouncycastle.cert.X509CRLHolder; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.cmp.*; import org.bouncycastle.cert.crmf.CRMFException; import org.bouncycastle.cert.crmf.EncryptedValueParser; import org.bouncycastle.cert.crmf.jcajce.JcaCertificateRequestMessageBuilder; import org.bouncycastle.cert.crmf.jcajce.JcaPKIArchiveControlBuilder; import org.bouncycastle.cert.crmf.jcajce.JceAsymmetricValueDecryptorGenerator; import org.bouncycastle.cert.jcajce.JcaX500NameUtil; import org.bouncycastle.cert.jcajce.JcaX509CRLConverter; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; import org.bouncycastle.cms.*; import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder; import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient; import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.ContentVerifierProvider; import org.bouncycastle.operator.InputDecryptor; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder; import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; import org.bouncycastle.util.io.Streams; import org.cryptable.pki.model.CertificationResult; import org.cryptable.pki.model.RevocationInput; import org.cryptable.pki.util.PKIKeyStore; import org.cryptable.pki.util.PKIKeyStoreException; import javax.security.auth.x500.X500Principal; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.math.BigInteger; import java.security.KeyFactory; import java.security.KeyPair; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.cert.*; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.RSAPrivateKeySpec; import java.text.ParseException; import java.util.*; /** * The messages class to communicate with the PKI system * User: davidtillemans * Date: 1/06/13 * Time: 12:33 * To change this template use File | Settings | File Templates. */ public class PKICMPMessages { private PKIKeyStore pkiKeyStore; private byte[] senderNonce; private byte[] transactionId; private BigInteger certificateID; private Extension[] extensions; private OptionalValidity optionalValidity; public PKICMPMessages() { transactionId = null; senderNonce = null; extensions = null; optionalValidity = null; } public PKIKeyStore getPkiKeyStore() { return pkiKeyStore; } public void setPkiKeyStore(PKIKeyStore keyStore) { this.pkiKeyStore = keyStore; } public BigInteger getCertificateID() { return certificateID; } public void setCertificateID(BigInteger certificateID) { this.certificateID = certificateID; } public void setTransactionId(byte[] transactionId) { this.transactionId = transactionId; } public byte[] getTransactionId() { return this.transactionId; } public byte[] getSenderNonce() { return senderNonce; } public Extension[] getExtensions() { return extensions; } public void setExtensions(Extension[] extensions) { this.extensions = extensions; } public OptionalValidity getOptionalValidity() { return optionalValidity; } public void setOptionalValidity(OptionalValidity optionalValidity) { this.optionalValidity = optionalValidity; } public void setValidity(Date notBefore, Date notAfter) { this.optionalValidity = new OptionalValidity(new Time(notBefore), new Time(notAfter)); } private byte[] createProtectedPKIMessage(PKIBody pkiBody) throws CMPException, OperatorCreationException, IOException, CertificateEncodingException, PKICMPMessageException { senderNonce = new byte[64]; pkiKeyStore.getSecureRandom().nextBytes(senderNonce); if (transactionId == null) { transactionId = new byte[64]; pkiKeyStore.getSecureRandom().nextBytes(transactionId); } ContentSigner signer = new JcaContentSignerBuilder("SHA1WithRSAEncryption") .setProvider(pkiKeyStore.getProvider()).build(pkiKeyStore.getSenderPrivateKey()); ProtectedPKIMessage message = new ProtectedPKIMessageBuilder( new GeneralName(JcaX500NameUtil.getSubject(pkiKeyStore.getSenderCertificate())), new GeneralName(JcaX500NameUtil.getSubject(pkiKeyStore.getRecipientCertificate()))) .setMessageTime(new Date()).setSenderNonce(senderNonce).setTransactionID(transactionId) .addCMPCertificate( new X509CertificateHolder(pkiKeyStore.getSenderCertificate().getEncoded())) .setBody(pkiBody).build(signer); return message.toASN1Structure().getEncoded(); } /** * Creates a certification request * * @param distinguishedName the distinguished name for the certificate * @param keyPair the key pair to certify, you have to remove the private key so the CA won't archive it * @return return the binary ASN.1 message for a certification request * @throws CertificateEncodingException * @throws CMSException * @throws CRMFException * @throws OperatorCreationException * @throws CMPException * @throws IOException */ private byte[] createCertificateMessage(String distinguishedName, KeyPair keyPair, int requestType) throws CertificateEncodingException, CMSException, CRMFException, OperatorCreationException, CMPException, IOException, PKICMPMessageException, NoSuchFieldException, IllegalAccessException { JcaCertificateRequestMessageBuilder certReqBuild = new JcaCertificateRequestMessageBuilder(BigInteger.ZERO); // Basic certificate requests certReqBuild.setSubject(new X500Name(distinguishedName)); // Add key pair if (keyPair != null) { byte[] bRSAKey = keyPair.getPublic().getEncoded(); certReqBuild.setPublicKey(new SubjectPublicKeyInfo(ASN1Sequence.getInstance(bRSAKey))); if (keyPair.getPrivate() != null) { certReqBuild.addControl( new JcaPKIArchiveControlBuilder(keyPair.getPrivate(), new X500Principal(distinguishedName)) .addRecipientGenerator( new JceKeyTransRecipientInfoGenerator(pkiKeyStore.getRecipientCertificate()) .setProvider(pkiKeyStore.getProvider())) .build(new JceCMSContentEncryptorBuilder( new ASN1ObjectIdentifier(CMSEnvelopedDataGenerator.DES_EDE3_CBC)) .setProvider(pkiKeyStore.getProvider()).build())); } } if (optionalValidity != null) { Field field = certReqBuild.getClass().getSuperclass().getDeclaredField("templateBuilder"); field.setAccessible(true); CertTemplateBuilder certTemplateBuilder = (CertTemplateBuilder) field.get(certReqBuild); certTemplateBuilder.setValidity(optionalValidity); } if (extensions != null) { for (Extension extension : extensions) certReqBuild.addExtension(extension.getExtnId(), extension.isCritical(), extension.getParsedValue()); } CertReqMessages certReqMsgs = new CertReqMessages(certReqBuild.build().toASN1Structure()); return createProtectedPKIMessage(new PKIBody(requestType, certReqMsgs)); } /** * Creates a initialization request, always local key generation * * @param distinguishedName the distinguished name for the certificate * @param keyPair the key pair to certify, you have to remove the private key so the CA won't archive it * @return return the binary ASN.1 message for a certification request * @throws CertificateEncodingException * @throws CMSException * @throws CRMFException * @throws OperatorCreationException * @throws CMPException * @throws IOException */ public byte[] createInitializationMessage(String distinguishedName, KeyPair keyPair) throws CertificateEncodingException, CMSException, CRMFException, OperatorCreationException, CMPException, IOException, PKICMPMessageException, NoSuchFieldException, IllegalAccessException { return createCertificateMessage(distinguishedName, keyPair, PKIBody.TYPE_INIT_REQ); } /** * Creates a certification request with local key generation * * @param distinguishedName the distinguished name for the certificate * @param keyPair the key pair to certify, you have to remove the private key so the CA won't archive it * @return return the binary ASN.1 message for a certification request * @throws CertificateEncodingException * @throws CMSException * @throws CRMFException * @throws OperatorCreationException * @throws CMPException * @throws IOException */ public byte[] createCertificateMessageWithLocalKey(String distinguishedName, KeyPair keyPair) throws CertificateEncodingException, CMSException, CRMFException, OperatorCreationException, CMPException, IOException, PKICMPMessageException, NoSuchFieldException, IllegalAccessException { return createCertificateMessage(distinguishedName, keyPair, PKIBody.TYPE_CERT_REQ); } /** * Creates a certification request with local key generation * * @param distinguishedName the distinguished name for the certificate * @return return the binary ASN.1 message for a certification request * @throws CertificateEncodingException * @throws CMSException * @throws CRMFException * @throws OperatorCreationException * @throws CMPException * @throws IOException */ public byte[] createCertificateMessageWithRemoteKey(String distinguishedName) throws CertificateEncodingException, CMSException, CRMFException, OperatorCreationException, CMPException, IOException, PKICMPMessageException, NoSuchFieldException, IllegalAccessException { return createCertificateMessage(distinguishedName, null, PKIBody.TYPE_CERT_REQ); } /** * Update a certification request with local key generation * * @param certificate to be updated * @return return the binary ASN.1 message for a certification request * @throws CertificateEncodingException * @throws CMSException * @throws CRMFException * @throws OperatorCreationException * @throws CMPException * @throws IOException */ public byte[] createKeyUpdateMessageWithLocalKey(X509Certificate certificate, KeyPair keyPair) throws CertificateEncodingException, CMSException, CRMFException, OperatorCreationException, CMPException, IOException, PKICMPMessageException, NoSuchFieldException, IllegalAccessException { JcaCertificateRequestMessageBuilder certReqBuild = new JcaCertificateRequestMessageBuilder(BigInteger.ZERO); X509CertificateHolder x509CertificateHolder = new JcaX509CertificateHolder(certificate); certReqBuild.setSubject(x509CertificateHolder.getSubject()); certReqBuild.setIssuer(x509CertificateHolder.getIssuer()); certReqBuild.setSerialNumber(x509CertificateHolder.getSerialNumber()); if (keyPair != null) { certReqBuild.setPublicKey(keyPair.getPublic()); if (keyPair.getPrivate() != null) { certReqBuild.addControl( new JcaPKIArchiveControlBuilder(keyPair.getPrivate(), x509CertificateHolder.getIssuer()) .addRecipientGenerator( new JceKeyTransRecipientInfoGenerator(pkiKeyStore.getRecipientCertificate()) .setProvider(pkiKeyStore.getProvider())) .build(new JceCMSContentEncryptorBuilder( new ASN1ObjectIdentifier(CMSEnvelopedDataGenerator.DES_EDE3_CBC)) .setProvider(pkiKeyStore.getProvider()).build())); } } else certReqBuild.setPublicKey(x509CertificateHolder.getSubjectPublicKeyInfo()); if (extensions != null) { for (Extension extension : extensions) certReqBuild.addExtension(extension.getExtnId(), extension.isCritical(), extension.getParsedValue()); } else { if (x509CertificateHolder.getExtensions() != null) { for (ASN1ObjectIdentifier oid : x509CertificateHolder.getExtensions().getExtensionOIDs()) { certReqBuild.addExtension(oid, x509CertificateHolder.getExtensions().getExtension(oid).isCritical(), x509CertificateHolder.getExtensions().getExtensionParsedValue(oid)); } } } OptionalValidity tempOptionalValidity; if (optionalValidity != null) { tempOptionalValidity = optionalValidity; } else { tempOptionalValidity = new OptionalValidity(new Time(x509CertificateHolder.getNotBefore()), new Time(x509CertificateHolder.getNotAfter())); } Field field = certReqBuild.getClass().getSuperclass().getDeclaredField("templateBuilder"); field.setAccessible(true); CertTemplateBuilder certTemplateBuilder = (CertTemplateBuilder) field.get(certReqBuild); certTemplateBuilder.setValidity(tempOptionalValidity); CertReqMessages certReqMsgs = new CertReqMessages(certReqBuild.build().toASN1Structure()); return createProtectedPKIMessage(new PKIBody(PKIBody.TYPE_KEY_UPDATE_REQ, certReqMsgs)); } /** * Update a certification request with remote key generation * * @param certificate to be updated * @return return the binary ASN.1 message for a certification request * @throws CertificateEncodingException * @throws CMSException * @throws CRMFException * @throws OperatorCreationException * @throws CMPException * @throws IOException */ public byte[] createKeyUpdateMessageWithRemoteKey(X509Certificate certificate) throws CertificateEncodingException, CMSException, CRMFException, OperatorCreationException, CMPException, IOException, PKICMPMessageException, NoSuchFieldException, IllegalAccessException { JcaCertificateRequestMessageBuilder certReqBuild = new JcaCertificateRequestMessageBuilder(BigInteger.ZERO); X509CertificateHolder x509CertificateHolder = new JcaX509CertificateHolder(certificate); certReqBuild.setSubject(x509CertificateHolder.getSubject()); certReqBuild.setIssuer(x509CertificateHolder.getIssuer()); certReqBuild.setSerialNumber(x509CertificateHolder.getSerialNumber()); if (extensions != null) { for (Extension extension : extensions) certReqBuild.addExtension(extension.getExtnId(), extension.isCritical(), extension.getParsedValue()); } else { if (x509CertificateHolder.getExtensions() != null) { for (ASN1ObjectIdentifier oid : x509CertificateHolder.getExtensions().getExtensionOIDs()) { certReqBuild.addExtension(oid, x509CertificateHolder.getExtensions().getExtension(oid).isCritical(), x509CertificateHolder.getExtensions().getExtensionParsedValue(oid)); } } } OptionalValidity tempOptionalValidity; if (optionalValidity != null) { tempOptionalValidity = optionalValidity; } else { tempOptionalValidity = new OptionalValidity(new Time(x509CertificateHolder.getNotBefore()), new Time(x509CertificateHolder.getNotAfter())); } Field field = certReqBuild.getClass().getSuperclass().getDeclaredField("templateBuilder"); field.setAccessible(true); CertTemplateBuilder certTemplateBuilder = (CertTemplateBuilder) field.get(certReqBuild); certTemplateBuilder.setValidity(tempOptionalValidity); CertReqMessages certReqMsgs = new CertReqMessages(certReqBuild.build().toASN1Structure()); return createProtectedPKIMessage(new PKIBody(PKIBody.TYPE_KEY_UPDATE_REQ, certReqMsgs)); } /** * Revoke a certificate * */ public byte[] createRevocationMessage(RevocationInput[] revocationInputs) throws CertificateEncodingException, CMSException, CRMFException, OperatorCreationException, CMPException, IOException, PKICMPMessageException, NoSuchFieldException, IllegalAccessException { List<RevDetails> revDetailsList = new ArrayList<RevDetails>(revocationInputs.length); for (RevocationInput revocationInput : revocationInputs) { List<Extension> extensions = new ArrayList<Extension>(); X509CertificateHolder x509CertificateHolder = new JcaX509CertificateHolder( revocationInput.getX509Certificate()); CertTemplateBuilder certTemplateBuilder = new CertTemplateBuilder(); // Template to fill in certTemplateBuilder.setSubject(x509CertificateHolder.getSubject()) .setIssuer(x509CertificateHolder.getIssuer()) .setSerialNumber(new ASN1Integer(x509CertificateHolder.getSerialNumber())) .setPublicKey(x509CertificateHolder.getSubjectPublicKeyInfo()); // Optional Revocation Extensions if (revocationInput.getReasonCode() != -1) { extensions.add(new Extension(Extension.reasonCode, false, new ReasonFlags(revocationInput.getReasonCode()).getEncoded())); } if (revocationInput.getInvalidityDate() != null) { extensions.add(new Extension(Extension.invalidityDate, false, new Time(revocationInput.getInvalidityDate()).getEncoded())); } if (extensions.size() == 0) { revDetailsList.add(new RevDetails(certTemplateBuilder.build())); } else { revDetailsList.add(new RevDetails(certTemplateBuilder.build(), new Extensions(extensions.toArray(new Extension[extensions.size()])))); } } RevReqContent revReqContent = new RevReqContent( revDetailsList.toArray(new RevDetails[revDetailsList.size()])); return createProtectedPKIMessage(new PKIBody(PKIBody.TYPE_REVOCATION_REQ, revReqContent)); } /** * The message to decode a certification response * * @param message * @return response message * @throws IOException * @throws PKICMPMessageException */ PKICMPResponse processResponse(byte[] message) throws IOException, PKICMPMessageException, CertificateException, OperatorCreationException, CMPException, PKIKeyStoreException, ParseException { CertificationResult certificationResult = new CertificationResult(); ProtectedPKIMessage pkiMessage = new ProtectedPKIMessage(new GeneralPKIMessage(message)); /* Verify Signature */ ContentVerifierProvider verifierProvider = new JcaContentVerifierProviderBuilder() .setProvider(pkiKeyStore.getProvider()).build(pkiKeyStore.getRecipientCertificate()); if (!pkiMessage.verify(verifierProvider)) { throw new PKICMPMessageException("E: Verification failed this is an untrusted Message [" + pkiMessage.getHeader().getSender() + "]"); } if (!Arrays.equals(senderNonce, pkiMessage.getHeader().getRecipNonce().getOctets())) throw new PKICMPMessageException( "E: Recipient Nonce in response does not correspond with Sender Nonce in request!"); if (pkiMessage.getHeader().getMessageTime() != null) { pkiKeyStore.verifyCertificate(pkiKeyStore.getRecipientCertificate(), pkiMessage.getHeader().getMessageTime().getDate()); } else { pkiKeyStore.verifyCertificate(pkiKeyStore.getRecipientCertificate(), new Date()); } PKICMPResponse pkicmpResponse = new PKICMPResponse(); pkicmpResponse.setPkiBody(pkiMessage.getBody()); pkicmpResponse.setPkiHeader(pkiMessage.getHeader()); X509CertificateHolder[] x509CertificateHolders = pkiMessage.getCertificates(); JcaX509CertificateConverter jcaX509CertificateConverter = new JcaX509CertificateConverter(); for (X509CertificateHolder x509CertificateHolder : x509CertificateHolders) { pkicmpResponse.getX509CertifificateList() .add(jcaX509CertificateConverter.getCertificate(x509CertificateHolder)); } return pkicmpResponse; } /** * Process the certification in the PKIBody content. This is used by the initialization process * certification and keyupdate process * * @param pkiBody * @return * @throws IOException * @throws CMSException * @throws CRMFException * @throws InvalidKeySpecException * @throws NoSuchAlgorithmException * @throws CertificateException */ CertificationResult processCertification(PKIBody pkiBody) throws IOException, CMSException, CRMFException, InvalidKeySpecException, NoSuchAlgorithmException, CertificateException { CertificationResult certificationResult = new CertificationResult(); CertRepMessage certRepMessage = CertRepMessage.getInstance(pkiBody.getContent()); CertResponse[] certResponses = certRepMessage.getResponse(); certificationResult.setCertificateId(certResponses[0].getCertReqId().getValue()); CMPCertificate certificate = certResponses[0].getCertifiedKeyPair().getCertOrEncCert().getCertificate(); certificationResult.setX509Certificate(new JcaX509CertificateConverter() .getCertificate(new X509CertificateHolder(certificate.getX509v3PKCert()))); EncryptedValue encPrivateKey = certResponses[0].getCertifiedKeyPair().getPrivateKey(); if (encPrivateKey != null) { JceAsymmetricValueDecryptorGenerator jceAsymmetricValueDecryptorGenerator = new JceAsymmetricValueDecryptorGenerator( pkiKeyStore.getSenderPrivateKey()); InputDecryptor decryptor = jceAsymmetricValueDecryptorGenerator.getValueDecryptor( encPrivateKey.getKeyAlg(), encPrivateKey.getSymmAlg(), encPrivateKey.getEncSymmKey().getBytes()); InputStream dataIn = decryptor .getInputStream(new ByteArrayInputStream(encPrivateKey.getEncValue().getBytes())); byte[] data = Streams.readAll(dataIn); PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(data); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); certificationResult.setPrivateKey(keyFactory.generatePrivate(pkcs8EncodedKeySpec)); } CMPCertificate[] caPubs = certRepMessage.getCaPubs(); for (CMPCertificate cmpCertificate : caPubs) { certificationResult.addX509CertificateToChain(new JcaX509CertificateConverter() .getCertificate(new X509CertificateHolder(cmpCertificate.getX509v3PKCert()))); } return certificationResult; } X509CRL processRevocation(PKIBody pkiBody) throws CRLException { JcaX509CRLConverter jcaX509CRLConverter = new JcaX509CRLConverter(); RevRepContent revRepContent = RevRepContent.getInstance(pkiBody.getContent()); return jcaX509CRLConverter.getCRL(new X509CRLHolder(revRepContent.getCrls()[0])); } /** * This creates a message to confirm a certification message * * @param x509Certificate the certificate to confirm * @return return the binary ASN.1 message to confirm certificate * @throws CertificateEncodingException * @throws IOException * @throws OperatorCreationException * @throws CMPException */ public byte[] createConfirmationMessage(X509Certificate x509Certificate, BigInteger certificateID) throws CertificateEncodingException, IOException, OperatorCreationException, CMPException, PKICMPMessageException { CertificateConfirmationContentBuilder certificateConfirmationContentBuilder = new CertificateConfirmationContentBuilder(); X509CertificateHolder x509CertificateHolder = new X509CertificateHolder(x509Certificate.getEncoded()); certificateConfirmationContentBuilder.addAcceptedCertificate(x509CertificateHolder, certificateID); return createProtectedPKIMessage( new PKIBody(PKIBody.TYPE_CERT_CONFIRM, certificateConfirmationContentBuilder .build(new JcaDigestCalculatorProviderBuilder().build()).toASN1Structure())); } }