diff options
Diffstat (limited to 'pkix/src/main/java/org/spongycastle/pkcs')
29 files changed, 2474 insertions, 0 deletions
diff --git a/pkix/src/main/java/org/spongycastle/pkcs/MacDataGenerator.java b/pkix/src/main/java/org/spongycastle/pkcs/MacDataGenerator.java new file mode 100644 index 00000000..c6667d8b --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/pkcs/MacDataGenerator.java @@ -0,0 +1,49 @@ +package org.spongycastle.pkcs; + + +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.asn1.pkcs.MacData; +import org.spongycastle.asn1.pkcs.PKCS12PBEParams; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.DigestInfo; +import org.spongycastle.operator.MacCalculator; + +class MacDataGenerator +{ + private PKCS12MacCalculatorBuilder builder; + + MacDataGenerator(PKCS12MacCalculatorBuilder builder) + { + this.builder = builder; + } + + public MacData build(char[] password, byte[] data) + throws PKCSException + { + MacCalculator macCalculator; + + try + { + macCalculator = builder.build(password); + + OutputStream out = macCalculator.getOutputStream(); + + out.write(data); + + out.close(); + } + catch (Exception e) + { + throw new PKCSException("unable to process data: " + e.getMessage(), e); + } + + AlgorithmIdentifier algId = macCalculator.getAlgorithmIdentifier(); + + DigestInfo dInfo = new DigestInfo(builder.getDigestAlgorithmIdentifier(), macCalculator.getMac()); + PKCS12PBEParams params = PKCS12PBEParams.getInstance(algId.getParameters()); + + return new MacData(dInfo, params.getIV(), params.getIterations().intValue()); + } +} diff --git a/pkix/src/main/java/org/spongycastle/pkcs/PKCS10CertificationRequest.java b/pkix/src/main/java/org/spongycastle/pkcs/PKCS10CertificationRequest.java new file mode 100644 index 00000000..c4ee749a --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/pkcs/PKCS10CertificationRequest.java @@ -0,0 +1,236 @@ +package org.spongycastle.pkcs; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +import org.spongycastle.asn1.ASN1Encoding; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.ASN1Set; +import org.spongycastle.asn1.pkcs.Attribute; +import org.spongycastle.asn1.pkcs.CertificationRequest; +import org.spongycastle.asn1.pkcs.CertificationRequestInfo; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.operator.ContentVerifier; +import org.spongycastle.operator.ContentVerifierProvider; + +/** + * Holding class for a PKCS#10 certification request. + */ +public class PKCS10CertificationRequest +{ + private static Attribute[] EMPTY_ARRAY = new Attribute[0]; + + private CertificationRequest certificationRequest; + + private static CertificationRequest parseBytes(byte[] encoding) + throws IOException + { + try + { + return CertificationRequest.getInstance(ASN1Primitive.fromByteArray(encoding)); + } + catch (ClassCastException e) + { + throw new PKCSIOException("malformed data: " + e.getMessage(), e); + } + catch (IllegalArgumentException e) + { + throw new PKCSIOException("malformed data: " + e.getMessage(), e); + } + } + + /** + * Create a PKCS10CertificationRequestHolder from an underlying ASN.1 structure. + * + * @param certificationRequest the underlying ASN.1 structure representing a request. + */ + public PKCS10CertificationRequest(CertificationRequest certificationRequest) + { + this.certificationRequest = certificationRequest; + } + + /** + * Create a PKCS10CertificationRequestHolder from the passed in bytes. + * + * @param encoded BER/DER encoding of the CertificationRequest structure. + * @throws IOException in the event of corrupted data, or an incorrect structure. + */ + public PKCS10CertificationRequest(byte[] encoded) + throws IOException + { + this(parseBytes(encoded)); + } + + /** + * Return the underlying ASN.1 structure for this request. + * + * @return a CertificateRequest object. + */ + public CertificationRequest toASN1Structure() + { + return certificationRequest; + } + + /** + * Return the subject on this request. + * + * @return the X500Name representing the request's subject. + */ + public X500Name getSubject() + { + return X500Name.getInstance(certificationRequest.getCertificationRequestInfo().getSubject()); + } + + /** + * Return the details of the signature algorithm used to create this request. + * + * @return the AlgorithmIdentifier describing the signature algorithm used to create this request. + */ + public AlgorithmIdentifier getSignatureAlgorithm() + { + return certificationRequest.getSignatureAlgorithm(); + } + + /** + * Return the bytes making up the signature associated with this request. + * + * @return the request signature bytes. + */ + public byte[] getSignature() + { + return certificationRequest.getSignature().getBytes(); + } + + /** + * Return the SubjectPublicKeyInfo describing the public key this request is carrying. + * + * @return the public key ASN.1 structure contained in the request. + */ + public SubjectPublicKeyInfo getSubjectPublicKeyInfo() + { + return certificationRequest.getCertificationRequestInfo().getSubjectPublicKeyInfo(); + } + + /** + * Return the attributes, if any associated with this request. + * + * @return an array of Attribute, zero length if none present. + */ + public Attribute[] getAttributes() + { + ASN1Set attrSet = certificationRequest.getCertificationRequestInfo().getAttributes(); + + if (attrSet == null) + { + return EMPTY_ARRAY; + } + + Attribute[] attrs = new Attribute[attrSet.size()]; + + for (int i = 0; i != attrSet.size(); i++) + { + attrs[i] = Attribute.getInstance(attrSet.getObjectAt(i)); + } + + return attrs; + } + + /** + * Return an array of attributes matching the passed in type OID. + * + * @param type the type of the attribute being looked for. + * @return an array of Attribute of the requested type, zero length if none present. + */ + public Attribute[] getAttributes(ASN1ObjectIdentifier type) + { + ASN1Set attrSet = certificationRequest.getCertificationRequestInfo().getAttributes(); + + if (attrSet == null) + { + return EMPTY_ARRAY; + } + + List list = new ArrayList(); + + for (int i = 0; i != attrSet.size(); i++) + { + Attribute attr = Attribute.getInstance(attrSet.getObjectAt(i)); + if (attr.getAttrType().equals(type)) + { + list.add(attr); + } + } + + if (list.size() == 0) + { + return EMPTY_ARRAY; + } + + return (Attribute[])list.toArray(new Attribute[list.size()]); + } + + public byte[] getEncoded() + throws IOException + { + return certificationRequest.getEncoded(); + } + + /** + * Validate the signature on the PKCS10 certification request in this holder. + * + * @param verifierProvider a ContentVerifierProvider that can generate a verifier for the signature. + * @return true if the signature is valid, false otherwise. + * @throws PKCSException if the signature cannot be processed or is inappropriate. + */ + public boolean isSignatureValid(ContentVerifierProvider verifierProvider) + throws PKCSException + { + CertificationRequestInfo requestInfo = certificationRequest.getCertificationRequestInfo(); + + ContentVerifier verifier; + + try + { + verifier = verifierProvider.get(certificationRequest.getSignatureAlgorithm()); + + OutputStream sOut = verifier.getOutputStream(); + + sOut.write(requestInfo.getEncoded(ASN1Encoding.DER)); + + sOut.close(); + } + catch (Exception e) + { + throw new PKCSException("unable to process signature: " + e.getMessage(), e); + } + + return verifier.verify(certificationRequest.getSignature().getBytes()); + } + + public boolean equals(Object o) + { + if (o == this) + { + return true; + } + + if (!(o instanceof PKCS10CertificationRequest)) + { + return false; + } + + PKCS10CertificationRequest other = (PKCS10CertificationRequest)o; + + return this.toASN1Structure().equals(other.toASN1Structure()); + } + + public int hashCode() + { + return this.toASN1Structure().hashCode(); + } +} diff --git a/pkix/src/main/java/org/spongycastle/pkcs/PKCS10CertificationRequestBuilder.java b/pkix/src/main/java/org/spongycastle/pkcs/PKCS10CertificationRequestBuilder.java new file mode 100644 index 00000000..d10c6fdd --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/pkcs/PKCS10CertificationRequestBuilder.java @@ -0,0 +1,156 @@ +package org.spongycastle.pkcs; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Encoding; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.DERBitString; +import org.spongycastle.asn1.DERSet; +import org.spongycastle.asn1.pkcs.Attribute; +import org.spongycastle.asn1.pkcs.CertificationRequest; +import org.spongycastle.asn1.pkcs.CertificationRequestInfo; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.operator.ContentSigner; + +/** + * A class for creating PKCS#10 Certification requests. + * <pre> + * CertificationRequest ::= SEQUENCE { + * certificationRequestInfo CertificationRequestInfo, + * signatureAlgorithm AlgorithmIdentifier{{ SignatureAlgorithms }}, + * signature BIT STRING + * } + * + * CertificationRequestInfo ::= SEQUENCE { + * version INTEGER { v1(0) } (v1,...), + * subject Name, + * subjectPKInfo SubjectPublicKeyInfo{{ PKInfoAlgorithms }}, + * attributes [0] Attributes{{ CRIAttributes }} + * } + * + * 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}) + * } + * </pre> + */ +public class PKCS10CertificationRequestBuilder +{ + private SubjectPublicKeyInfo publicKeyInfo; + private X500Name subject; + private List attributes = new ArrayList(); + private boolean leaveOffEmpty = false; + + /** + * Basic constructor. + * + * @param subject the X.500 Name defining the certificate subject this request is for. + * @param publicKeyInfo the info structure for the public key to be associated with this subject. + */ + public PKCS10CertificationRequestBuilder(X500Name subject, SubjectPublicKeyInfo publicKeyInfo) + { + this.subject = subject; + this.publicKeyInfo = publicKeyInfo; + } + + /** + * Add an attribute to the certification request we are building. + * + * @param attrType the OID giving the type of the attribute. + * @param attrValue the ASN.1 structure that forms the value of the attribute. + * @return this builder object. + */ + public PKCS10CertificationRequestBuilder addAttribute(ASN1ObjectIdentifier attrType, ASN1Encodable attrValue) + { + attributes.add(new Attribute(attrType, new DERSet(attrValue))); + + return this; + } + + /** + * Add an attribute with multiple values to the certification request we are building. + * + * @param attrType the OID giving the type of the attribute. + * @param attrValues an array of ASN.1 structures that form the value of the attribute. + * @return this builder object. + */ + public PKCS10CertificationRequestBuilder addAttribute(ASN1ObjectIdentifier attrType, ASN1Encodable[] attrValues) + { + attributes.add(new Attribute(attrType, new DERSet(attrValues))); + + return this; + } + + /** + * The attributes field in PKCS10 should encoded to an empty tagged set if there are + * no attributes. Some CAs will reject requests with the attribute field present. + * + * @param leaveOffEmpty true if empty attributes should be left out of the encoding false otherwise. + * @return this builder object. + */ + public PKCS10CertificationRequestBuilder setLeaveOffEmptyAttributes(boolean leaveOffEmpty) + { + this.leaveOffEmpty = leaveOffEmpty; + + return this; + } + + /** + * Generate an PKCS#10 request based on the past in signer. + * + * @param signer the content signer to be used to generate the signature validating the certificate. + * @return a holder containing the resulting PKCS#10 certification request. + */ + public PKCS10CertificationRequest build( + ContentSigner signer) + { + CertificationRequestInfo info; + + if (attributes.isEmpty()) + { + if (leaveOffEmpty) + { + info = new CertificationRequestInfo(subject, publicKeyInfo, null); + } + else + { + info = new CertificationRequestInfo(subject, publicKeyInfo, new DERSet()); + } + } + else + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + for (Iterator it = attributes.iterator(); it.hasNext();) + { + v.add(Attribute.getInstance(it.next())); + } + + info = new CertificationRequestInfo(subject, publicKeyInfo, new DERSet(v)); + } + + try + { + OutputStream sOut = signer.getOutputStream(); + + sOut.write(info.getEncoded(ASN1Encoding.DER)); + + sOut.close(); + + return new PKCS10CertificationRequest(new CertificationRequest(info, signer.getAlgorithmIdentifier(), new DERBitString(signer.getSignature()))); + } + catch (IOException e) + { + throw new IllegalStateException("cannot produce certification request signature"); + } + } +} diff --git a/pkix/src/main/java/org/spongycastle/pkcs/PKCS12MacCalculatorBuilder.java b/pkix/src/main/java/org/spongycastle/pkcs/PKCS12MacCalculatorBuilder.java new file mode 100644 index 00000000..be82e990 --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/pkcs/PKCS12MacCalculatorBuilder.java @@ -0,0 +1,13 @@ +package org.spongycastle.pkcs; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.operator.MacCalculator; +import org.spongycastle.operator.OperatorCreationException; + +public interface PKCS12MacCalculatorBuilder +{ + MacCalculator build(char[] password) + throws OperatorCreationException; + + AlgorithmIdentifier getDigestAlgorithmIdentifier(); +} diff --git a/pkix/src/main/java/org/spongycastle/pkcs/PKCS12MacCalculatorBuilderProvider.java b/pkix/src/main/java/org/spongycastle/pkcs/PKCS12MacCalculatorBuilderProvider.java new file mode 100644 index 00000000..52c9d1cd --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/pkcs/PKCS12MacCalculatorBuilderProvider.java @@ -0,0 +1,8 @@ +package org.spongycastle.pkcs; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +public interface PKCS12MacCalculatorBuilderProvider +{ + PKCS12MacCalculatorBuilder get(AlgorithmIdentifier algorithmIdentifier); +} diff --git a/pkix/src/main/java/org/spongycastle/pkcs/PKCS12PfxPdu.java b/pkix/src/main/java/org/spongycastle/pkcs/PKCS12PfxPdu.java new file mode 100644 index 00000000..6a229e48 --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/pkcs/PKCS12PfxPdu.java @@ -0,0 +1,161 @@ +package org.spongycastle.pkcs; + +import java.io.IOException; + +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.pkcs.ContentInfo; +import org.spongycastle.asn1.pkcs.MacData; +import org.spongycastle.asn1.pkcs.PKCS12PBEParams; +import org.spongycastle.asn1.pkcs.Pfx; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cert.CertIOException; +import org.spongycastle.util.Arrays; + +/** + * A holding class for the PKCS12 Pfx structure. + */ +public class PKCS12PfxPdu +{ + private Pfx pfx; + + private static Pfx parseBytes(byte[] pfxEncoding) + throws IOException + { + try + { + return Pfx.getInstance(ASN1Primitive.fromByteArray(pfxEncoding)); + } + catch (ClassCastException e) + { + throw new CertIOException("malformed data: " + e.getMessage(), e); + } + catch (IllegalArgumentException e) + { + throw new CertIOException("malformed data: " + e.getMessage(), e); + } + } + + public PKCS12PfxPdu(Pfx pfx) + { + this.pfx = pfx; + } + + public PKCS12PfxPdu(byte[] pfx) + throws IOException + { + this(parseBytes(pfx)); + } + + /** + * Return the content infos in the AuthenticatedSafe contained in this Pfx. + * + * @return an array of ContentInfo. + */ + public ContentInfo[] getContentInfos() + { + ASN1Sequence seq = ASN1Sequence.getInstance(ASN1OctetString.getInstance(this.pfx.getAuthSafe().getContent()).getOctets()); + ContentInfo[] content = new ContentInfo[seq.size()]; + + for (int i = 0; i != seq.size(); i++) + { + content[i] = ContentInfo.getInstance(seq.getObjectAt(i)); + } + + return content; + } + + /** + * Return whether or not there is MAC attached to this file. + * + * @return true if there is, false otherwise. + */ + public boolean hasMac() + { + return pfx.getMacData() != null; + } + + /** + * Return the algorithm identifier describing the MAC algorithm + * + * @return the AlgorithmIdentifier representing the MAC algorithm, null if none present. + */ + public AlgorithmIdentifier getMacAlgorithmID() + { + MacData md = pfx.getMacData(); + + if (md != null) + { + return md.getMac().getAlgorithmId(); + } + + return null; + } + + /** + * Verify the MacData attached to the PFX is consistent with what is expected. + * + * @param macCalcProviderBuilder provider builder for the calculator for the MAC + * @param password password to use + * @return true if mac data is valid, false otherwise. + * @throws PKCSException if there is a problem evaluating the MAC. + * @throws IllegalStateException if no MAC is actually present + */ + public boolean isMacValid(PKCS12MacCalculatorBuilderProvider macCalcProviderBuilder, char[] password) + throws PKCSException + { + if (hasMac()) + { + MacData pfxmData = pfx.getMacData(); + MacDataGenerator mdGen = new MacDataGenerator(macCalcProviderBuilder.get(new AlgorithmIdentifier(pfxmData.getMac().getAlgorithmId().getAlgorithm(), new PKCS12PBEParams(pfxmData.getSalt(), pfxmData.getIterationCount().intValue())))); + + try + { + MacData mData = mdGen.build( + password, + ASN1OctetString.getInstance(pfx.getAuthSafe().getContent()).getOctets()); + + return Arrays.constantTimeAreEqual(mData.getEncoded(), pfx.getMacData().getEncoded()); + } + catch (IOException e) + { + throw new PKCSException("unable to process AuthSafe: " + e.getMessage()); + } + } + + throw new IllegalStateException("no MAC present on PFX"); + } + + /** + * Return the underlying ASN.1 object. + * + * @return a Pfx object. + */ + public Pfx toASN1Structure() + { + return pfx; + } + + public byte[] getEncoded() + throws IOException + { + return toASN1Structure().getEncoded(); + } + + /** + * Return a Pfx with the outer wrapper encoded as asked for. For example, Pfx is a usually + * a BER encoded object, to get one with DefiniteLength encoding use: + * <pre> + * getEncoded(ASN1Encoding.DL) + * </pre> + * @param encoding encoding style (ASN1Encoding.DER, ASN1Encoding.DL, ASN1Encoding.BER) + * @return a byte array containing the encoded object. + * @throws IOException + */ + public byte[] getEncoded(String encoding) + throws IOException + { + return toASN1Structure().getEncoded(encoding); + } +} diff --git a/pkix/src/main/java/org/spongycastle/pkcs/PKCS12PfxPduBuilder.java b/pkix/src/main/java/org/spongycastle/pkcs/PKCS12PfxPduBuilder.java new file mode 100644 index 00000000..a9cb0b5b --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/pkcs/PKCS12PfxPduBuilder.java @@ -0,0 +1,179 @@ +package org.spongycastle.pkcs; + +import java.io.IOException; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.asn1.DLSequence; +import org.spongycastle.asn1.pkcs.AuthenticatedSafe; +import org.spongycastle.asn1.pkcs.ContentInfo; +import org.spongycastle.asn1.pkcs.MacData; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.pkcs.Pfx; +import org.spongycastle.cms.CMSEncryptedDataGenerator; +import org.spongycastle.cms.CMSException; +import org.spongycastle.cms.CMSProcessableByteArray; +import org.spongycastle.operator.OutputEncryptor; + +/** + * A builder for the PKCS#12 Pfx key and certificate store. + * <p> + * For example: you can build a basic key store for the user owning privKey as follows: + * </p> + * <pre> + * X509Certificate[] chain = .... + * PublicKey pubKey = .... + * PrivateKey privKey = .... + * JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils(); + * + * PKCS12SafeBagBuilder taCertBagBuilder = new JcaPKCS12SafeBagBuilder(chain[2]); + * + * taCertBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString("Bouncy Primary Certificate")); + * + * PKCS12SafeBagBuilder caCertBagBuilder = new JcaPKCS12SafeBagBuilder(chain[1]); + * + * caCertBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString("Bouncy Intermediate Certificate")); + * + * PKCS12SafeBagBuilder eeCertBagBuilder = new JcaPKCS12SafeBagBuilder(chain[0]); + * + * eeCertBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString("Eric's Key")); + * eeCertBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_localKeyId, extUtils.createSubjectKeyIdentifier(pubKey)); + * + * PKCS12SafeBagBuilder keyBagBuilder = new JcaPKCS12SafeBagBuilder(privKey, new BcPKCS12PBEOutputEncryptorBuilder(PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC, new CBCBlockCipher(new DESedeEngine())).build(passwd)); + * + * keyBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString("Eric's Key")); + * keyBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_localKeyId, extUtils.createSubjectKeyIdentifier(pubKey)); + * + * // + * // construct the actual key store + * // + * PKCS12PfxPduBuilder pfxPduBuilder = new PKCS12PfxPduBuilder(); + * + * PKCS12SafeBag[] certs = new PKCS12SafeBag[3]; + * + * certs[0] = eeCertBagBuilder.build(); + * certs[1] = caCertBagBuilder.build(); + * certs[2] = taCertBagBuilder.build(); + * + * pfxPduBuilder.addEncryptedData(new BcPKCS12PBEOutputEncryptorBuilder(PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC2_CBC, new CBCBlockCipher(new RC2Engine())).build(passwd), certs); + * + * pfxPduBuilder.addData(keyBagBuilder.build()); + * + * PKCS12PfxPdu pfx = pfxPduBuilder.build(new BcPKCS12MacCalculatorBuilder(), passwd); + * </pre> + * + */ +public class PKCS12PfxPduBuilder +{ + private ASN1EncodableVector dataVector = new ASN1EncodableVector(); + + /** + * Add a SafeBag that is to be included as is. + * + * @param data the SafeBag to add. + * @return this builder. + * @throws IOException + */ + public PKCS12PfxPduBuilder addData(PKCS12SafeBag data) + throws IOException + { + dataVector.add(new ContentInfo(PKCSObjectIdentifiers.data, new DEROctetString(new DLSequence(data.toASN1Structure()).getEncoded()))); + + return this; + } + + /** + * Add a SafeBag that is to be wrapped in a EncryptedData object. + * + * @param dataEncryptor the encryptor to use for encoding the data. + * @param data the SafeBag to include. + * @return this builder. + * @throws IOException if a issue occurs processing the data. + */ + public PKCS12PfxPduBuilder addEncryptedData(OutputEncryptor dataEncryptor, PKCS12SafeBag data) + throws IOException + { + return addEncryptedData(dataEncryptor, new DERSequence(data.toASN1Structure())); + } + + /** + * Add a set of SafeBags that are to be wrapped in a EncryptedData object. + * + * @param dataEncryptor the encryptor to use for encoding the data. + * @param data the SafeBags to include. + * @return this builder. + * @throws IOException if a issue occurs processing the data. + */ + public PKCS12PfxPduBuilder addEncryptedData(OutputEncryptor dataEncryptor, PKCS12SafeBag[] data) + throws IOException + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + for (int i = 0; i != data.length; i++) + { + v.add(data[i].toASN1Structure()); + } + + return addEncryptedData(dataEncryptor, new DLSequence(v)); + } + + private PKCS12PfxPduBuilder addEncryptedData(OutputEncryptor dataEncryptor, ASN1Sequence data) + throws IOException + { + CMSEncryptedDataGenerator envGen = new CMSEncryptedDataGenerator(); + + try + { + dataVector.add(envGen.generate(new CMSProcessableByteArray(data.getEncoded()), dataEncryptor).toASN1Structure()); + } + catch (CMSException e) + { + throw new PKCSIOException(e.getMessage(), e.getCause()); + } + + return this; + } + + /** + * Build the Pfx structure, protecting it with a MAC calculated against the passed in password. + * + * @param macCalcBuilder a builder for a PKCS12 mac calculator. + * @param password the password to use. + * @return a Pfx object. + * @throws PKCSException on a encoding or processing error. + */ + public PKCS12PfxPdu build(PKCS12MacCalculatorBuilder macCalcBuilder, char[] password) + throws PKCSException + { + AuthenticatedSafe auth = AuthenticatedSafe.getInstance(new DLSequence(dataVector)); + byte[] encAuth; + + try + { + encAuth = auth.getEncoded(); + } + catch (IOException e) + { + throw new PKCSException("unable to encode AuthenticatedSafe: " + e.getMessage(), e); + } + + ContentInfo mainInfo = new ContentInfo(PKCSObjectIdentifiers.data, new DEROctetString(encAuth)); + MacData mData = null; + + if (macCalcBuilder != null) + { + MacDataGenerator mdGen = new MacDataGenerator(macCalcBuilder); + + mData = mdGen.build(password, encAuth); + } + + // + // output the Pfx + // + Pfx pfx = new Pfx(mainInfo, mData); + + return new PKCS12PfxPdu(pfx); + } +} diff --git a/pkix/src/main/java/org/spongycastle/pkcs/PKCS12SafeBag.java b/pkix/src/main/java/org/spongycastle/pkcs/PKCS12SafeBag.java new file mode 100644 index 00000000..0ce0887e --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/pkcs/PKCS12SafeBag.java @@ -0,0 +1,93 @@ +package org.spongycastle.pkcs; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.ASN1Set; +import org.spongycastle.asn1.pkcs.Attribute; +import org.spongycastle.asn1.pkcs.CRLBag; +import org.spongycastle.asn1.pkcs.CertBag; +import org.spongycastle.asn1.pkcs.EncryptedPrivateKeyInfo; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.pkcs.PrivateKeyInfo; +import org.spongycastle.asn1.pkcs.SafeBag; +import org.spongycastle.asn1.x509.Certificate; +import org.spongycastle.asn1.x509.CertificateList; +import org.spongycastle.cert.X509CRLHolder; +import org.spongycastle.cert.X509CertificateHolder; + +public class PKCS12SafeBag +{ + public static final ASN1ObjectIdentifier friendlyNameAttribute = PKCSObjectIdentifiers.pkcs_9_at_friendlyName; + public static final ASN1ObjectIdentifier localKeyIdAttribute = PKCSObjectIdentifiers.pkcs_9_at_localKeyId; + + private SafeBag safeBag; + + public PKCS12SafeBag(SafeBag safeBag) + { + this.safeBag = safeBag; + } + + /** + * Return the underlying ASN.1 structure for this safe bag. + * + * @return a SafeBag + */ + public SafeBag toASN1Structure() + { + return safeBag; + } + + /** + * Return the BagId giving the type of content in the bag. + * + * @return the bagId + */ + public ASN1ObjectIdentifier getType() + { + return safeBag.getBagId(); + } + + public Attribute[] getAttributes() + { + ASN1Set attrs = safeBag.getBagAttributes(); + + if (attrs == null) + { + return null; + } + + Attribute[] attributes = new Attribute[attrs.size()]; + for (int i = 0; i != attrs.size(); i++) + { + attributes[i] = Attribute.getInstance(attrs.getObjectAt(i)); + } + + return attributes; + } + + public Object getBagValue() + { + if (getType().equals(PKCSObjectIdentifiers.pkcs8ShroudedKeyBag)) + { + return new PKCS8EncryptedPrivateKeyInfo(EncryptedPrivateKeyInfo.getInstance(safeBag.getBagValue())); + } + if (getType().equals(PKCSObjectIdentifiers.certBag)) + { + CertBag certBag = CertBag.getInstance(safeBag.getBagValue()); + + return new X509CertificateHolder(Certificate.getInstance(ASN1OctetString.getInstance(certBag.getCertValue()).getOctets())); + } + if (getType().equals(PKCSObjectIdentifiers.keyBag)) + { + return PrivateKeyInfo.getInstance(safeBag.getBagValue()); + } + if (getType().equals(PKCSObjectIdentifiers.crlBag)) + { + CRLBag crlBag = CRLBag.getInstance(safeBag.getBagValue()); + + return new X509CRLHolder(CertificateList.getInstance(ASN1OctetString.getInstance(crlBag.getCRLValue()).getOctets())); + } + + return safeBag.getBagValue(); + } +} diff --git a/pkix/src/main/java/org/spongycastle/pkcs/PKCS12SafeBagBuilder.java b/pkix/src/main/java/org/spongycastle/pkcs/PKCS12SafeBagBuilder.java new file mode 100644 index 00000000..d60bd5a5 --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/pkcs/PKCS12SafeBagBuilder.java @@ -0,0 +1,76 @@ +package org.spongycastle.pkcs; + +import java.io.IOException; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.DERSet; +import org.spongycastle.asn1.pkcs.Attribute; +import org.spongycastle.asn1.pkcs.CertBag; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.pkcs.PrivateKeyInfo; +import org.spongycastle.asn1.pkcs.SafeBag; +import org.spongycastle.asn1.x509.Certificate; +import org.spongycastle.asn1.x509.CertificateList; +import org.spongycastle.cert.X509CRLHolder; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.operator.OutputEncryptor; + +public class PKCS12SafeBagBuilder +{ + private ASN1ObjectIdentifier bagType; + private ASN1Encodable bagValue; + private ASN1EncodableVector bagAttrs = new ASN1EncodableVector(); + + public PKCS12SafeBagBuilder(PrivateKeyInfo privateKeyInfo, OutputEncryptor encryptor) + { + this.bagType = PKCSObjectIdentifiers.pkcs8ShroudedKeyBag; + this.bagValue = new PKCS8EncryptedPrivateKeyInfoBuilder(privateKeyInfo).build(encryptor).toASN1Structure(); + } + + public PKCS12SafeBagBuilder(PrivateKeyInfo privateKeyInfo) + { + this.bagType = PKCSObjectIdentifiers.keyBag; + this.bagValue = privateKeyInfo; + } + + public PKCS12SafeBagBuilder(X509CertificateHolder certificate) + throws IOException + { + this(certificate.toASN1Structure()); + } + + public PKCS12SafeBagBuilder(X509CRLHolder crl) + throws IOException + { + this(crl.toASN1Structure()); + } + + public PKCS12SafeBagBuilder(Certificate certificate) + throws IOException + { + this.bagType = PKCSObjectIdentifiers.certBag; + this.bagValue = new CertBag(PKCSObjectIdentifiers.x509Certificate, new DEROctetString(certificate.getEncoded())); + } + + public PKCS12SafeBagBuilder(CertificateList crl) + throws IOException + { + this.bagType = PKCSObjectIdentifiers.crlBag; + this.bagValue = new CertBag(PKCSObjectIdentifiers.x509Crl, new DEROctetString(crl.getEncoded())); + } + + public PKCS12SafeBagBuilder addBagAttribute(ASN1ObjectIdentifier attrType, ASN1Encodable attrValue) + { + bagAttrs.add(new Attribute(attrType, new DERSet(attrValue))); + + return this; + } + + public PKCS12SafeBag build() + { + return new PKCS12SafeBag(new SafeBag(bagType, bagValue, new DERSet(bagAttrs))); + } +} diff --git a/pkix/src/main/java/org/spongycastle/pkcs/PKCS12SafeBagFactory.java b/pkix/src/main/java/org/spongycastle/pkcs/PKCS12SafeBagFactory.java new file mode 100644 index 00000000..36f53723 --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/pkcs/PKCS12SafeBagFactory.java @@ -0,0 +1,58 @@ +package org.spongycastle.pkcs; + +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.pkcs.ContentInfo; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.pkcs.SafeBag; +import org.spongycastle.cms.CMSEncryptedData; +import org.spongycastle.cms.CMSException; +import org.spongycastle.operator.InputDecryptorProvider; + +public class PKCS12SafeBagFactory +{ + private ASN1Sequence safeBagSeq; + + public PKCS12SafeBagFactory(ContentInfo info) + { + if (info.getContentType().equals(PKCSObjectIdentifiers.encryptedData)) + { + throw new IllegalArgumentException("encryptedData requires constructor with decryptor."); + } + + this.safeBagSeq = ASN1Sequence.getInstance(ASN1OctetString.getInstance(info.getContent()).getOctets()); + } + + public PKCS12SafeBagFactory(ContentInfo info, InputDecryptorProvider inputDecryptorProvider) + throws PKCSException + { + if (info.getContentType().equals(PKCSObjectIdentifiers.encryptedData)) + { + CMSEncryptedData encData = new CMSEncryptedData(org.spongycastle.asn1.cms.ContentInfo.getInstance(info)); + + try + { + this.safeBagSeq = ASN1Sequence.getInstance(encData.getContent(inputDecryptorProvider)); + } + catch (CMSException e) + { + throw new PKCSException("unable to extract data: " + e.getMessage(), e); + } + return; + } + + throw new IllegalArgumentException("encryptedData requires constructor with decryptor."); + } + + public PKCS12SafeBag[] getSafeBags() + { + PKCS12SafeBag[] safeBags = new PKCS12SafeBag[safeBagSeq.size()]; + + for (int i = 0; i != safeBagSeq.size(); i++) + { + safeBags[i] = new PKCS12SafeBag(SafeBag.getInstance(safeBagSeq.getObjectAt(i))); + } + + return safeBags; + } +} diff --git a/pkix/src/main/java/org/spongycastle/pkcs/PKCS8EncryptedPrivateKeyInfo.java b/pkix/src/main/java/org/spongycastle/pkcs/PKCS8EncryptedPrivateKeyInfo.java new file mode 100644 index 00000000..1f41fb67 --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/pkcs/PKCS8EncryptedPrivateKeyInfo.java @@ -0,0 +1,76 @@ +package org.spongycastle.pkcs; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.pkcs.EncryptedPrivateKeyInfo; +import org.spongycastle.asn1.pkcs.PrivateKeyInfo; +import org.spongycastle.cert.CertIOException; +import org.spongycastle.operator.InputDecryptor; +import org.spongycastle.operator.InputDecryptorProvider; +import org.spongycastle.util.io.Streams; + +/** + * Holding class for a PKCS#8 EncryptedPrivateKeyInfo structure. + */ +public class PKCS8EncryptedPrivateKeyInfo +{ + private EncryptedPrivateKeyInfo encryptedPrivateKeyInfo; + + private static EncryptedPrivateKeyInfo parseBytes(byte[] pkcs8Encoding) + throws IOException + { + try + { + return EncryptedPrivateKeyInfo.getInstance(ASN1Primitive.fromByteArray(pkcs8Encoding)); + } + catch (ClassCastException e) + { + throw new CertIOException("malformed data: " + e.getMessage(), e); + } + catch (IllegalArgumentException e) + { + throw new CertIOException("malformed data: " + e.getMessage(), e); + } + } + + public PKCS8EncryptedPrivateKeyInfo(EncryptedPrivateKeyInfo encryptedPrivateKeyInfo) + { + this.encryptedPrivateKeyInfo = encryptedPrivateKeyInfo; + } + + public PKCS8EncryptedPrivateKeyInfo(byte[] encryptedPrivateKeyInfo) + throws IOException + { + this(parseBytes(encryptedPrivateKeyInfo)); + } + + public EncryptedPrivateKeyInfo toASN1Structure() + { + return encryptedPrivateKeyInfo; + } + + public byte[] getEncoded() + throws IOException + { + return encryptedPrivateKeyInfo.getEncoded(); + } + + public PrivateKeyInfo decryptPrivateKeyInfo(InputDecryptorProvider inputDecryptorProvider) + throws PKCSException + { + try + { + InputDecryptor decrytor = inputDecryptorProvider.get(encryptedPrivateKeyInfo.getEncryptionAlgorithm()); + + ByteArrayInputStream encIn = new ByteArrayInputStream(encryptedPrivateKeyInfo.getEncryptedData()); + + return PrivateKeyInfo.getInstance(Streams.readAll(decrytor.getInputStream(encIn))); + } + catch (Exception e) + { + throw new PKCSException("unable to read encrypted data: " + e.getMessage(), e); + } + } +} diff --git a/pkix/src/main/java/org/spongycastle/pkcs/PKCS8EncryptedPrivateKeyInfoBuilder.java b/pkix/src/main/java/org/spongycastle/pkcs/PKCS8EncryptedPrivateKeyInfoBuilder.java new file mode 100644 index 00000000..3897c40a --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/pkcs/PKCS8EncryptedPrivateKeyInfoBuilder.java @@ -0,0 +1,54 @@ +package org.spongycastle.pkcs; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.asn1.pkcs.EncryptedPrivateKeyInfo; +import org.spongycastle.asn1.pkcs.PrivateKeyInfo; +import org.spongycastle.operator.OutputEncryptor; + +/** + * A class for creating EncryptedPrivateKeyInfo structures. + * <pre> + * EncryptedPrivateKeyInfo ::= SEQUENCE { + * encryptionAlgorithm AlgorithmIdentifier {{KeyEncryptionAlgorithms}}, + * encryptedData EncryptedData + * } + * + * EncryptedData ::= OCTET STRING + * + * KeyEncryptionAlgorithms ALGORITHM-IDENTIFIER ::= { + * ... -- For local profiles + * } + * </pre> + */ +public class PKCS8EncryptedPrivateKeyInfoBuilder +{ + private PrivateKeyInfo privateKeyInfo; + + public PKCS8EncryptedPrivateKeyInfoBuilder(PrivateKeyInfo privateKeyInfo) + { + this.privateKeyInfo = privateKeyInfo; + } + + public PKCS8EncryptedPrivateKeyInfo build( + OutputEncryptor encryptor) + { + try + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + OutputStream cOut = encryptor.getOutputStream(bOut); + + cOut.write(privateKeyInfo.getEncoded()); + + cOut.close(); + + return new PKCS8EncryptedPrivateKeyInfo(new EncryptedPrivateKeyInfo(encryptor.getAlgorithmIdentifier(), bOut.toByteArray())); + } + catch (IOException e) + { + throw new IllegalStateException("cannot encode privateKeyInfo"); + } + } +} diff --git a/pkix/src/main/java/org/spongycastle/pkcs/PKCSException.java b/pkix/src/main/java/org/spongycastle/pkcs/PKCSException.java new file mode 100644 index 00000000..201dde93 --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/pkcs/PKCSException.java @@ -0,0 +1,27 @@ +package org.spongycastle.pkcs; + +/** + * General checked Exception thrown in the cert package and its sub-packages. + */ +public class PKCSException + extends Exception +{ + private Throwable cause; + + public PKCSException(String msg, Throwable cause) + { + super(msg); + + this.cause = cause; + } + + public PKCSException(String msg) + { + super(msg); + } + + public Throwable getCause() + { + return cause; + } +} diff --git a/pkix/src/main/java/org/spongycastle/pkcs/PKCSIOException.java b/pkix/src/main/java/org/spongycastle/pkcs/PKCSIOException.java new file mode 100644 index 00000000..0352829b --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/pkcs/PKCSIOException.java @@ -0,0 +1,29 @@ +package org.spongycastle.pkcs; + +import java.io.IOException; + +/** + * General IOException thrown in the cert package and its sub-packages. + */ +public class PKCSIOException + extends IOException +{ + private Throwable cause; + + public PKCSIOException(String msg, Throwable cause) + { + super(msg); + + this.cause = cause; + } + + public PKCSIOException(String msg) + { + super(msg); + } + + public Throwable getCause() + { + return cause; + } +} diff --git a/pkix/src/main/java/org/spongycastle/pkcs/bc/BcPKCS10CertificationRequest.java b/pkix/src/main/java/org/spongycastle/pkcs/bc/BcPKCS10CertificationRequest.java new file mode 100644 index 00000000..6ac22464 --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/pkcs/bc/BcPKCS10CertificationRequest.java @@ -0,0 +1,42 @@ +package org.spongycastle.pkcs.bc; + +import java.io.IOException; + +import org.spongycastle.asn1.pkcs.CertificationRequest; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.crypto.util.PublicKeyFactory; +import org.spongycastle.pkcs.PKCS10CertificationRequest; +import org.spongycastle.pkcs.PKCSException; + +public class BcPKCS10CertificationRequest + extends PKCS10CertificationRequest +{ + public BcPKCS10CertificationRequest(CertificationRequest certificationRequest) + { + super(certificationRequest); + } + + public BcPKCS10CertificationRequest(byte[] encoding) + throws IOException + { + super(encoding); + } + + public BcPKCS10CertificationRequest(PKCS10CertificationRequest requestHolder) + { + super(requestHolder.toASN1Structure()); + } + + public AsymmetricKeyParameter getPublicKey() + throws PKCSException + { + try + { + return PublicKeyFactory.createKey(this.getSubjectPublicKeyInfo()); + } + catch (IOException e) + { + throw new PKCSException("error extracting key encoding: " + e.getMessage(), e); + } + } +} diff --git a/pkix/src/main/java/org/spongycastle/pkcs/bc/BcPKCS10CertificationRequestBuilder.java b/pkix/src/main/java/org/spongycastle/pkcs/bc/BcPKCS10CertificationRequestBuilder.java new file mode 100644 index 00000000..1590a663 --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/pkcs/bc/BcPKCS10CertificationRequestBuilder.java @@ -0,0 +1,28 @@ +package org.spongycastle.pkcs.bc; + +import java.io.IOException; + +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.crypto.util.SubjectPublicKeyInfoFactory; +import org.spongycastle.pkcs.PKCS10CertificationRequestBuilder; + +/** + * Extension of the PKCS#10 builder to support AsymmetricKey objects. + */ +public class BcPKCS10CertificationRequestBuilder + extends PKCS10CertificationRequestBuilder +{ + /** + * Create a PKCS#10 builder for the passed in subject and JCA public key. + * + * @param subject an X500Name containing the subject associated with the request we are building. + * @param publicKey a JCA public key that is to be associated with the request we are building. + * @throws IOException if there is a problem encoding the public key. + */ + public BcPKCS10CertificationRequestBuilder(X500Name subject, AsymmetricKeyParameter publicKey) + throws IOException + { + super(subject, SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(publicKey)); + } +} diff --git a/pkix/src/main/java/org/spongycastle/pkcs/bc/BcPKCS12MacCalculatorBuilder.java b/pkix/src/main/java/org/spongycastle/pkcs/bc/BcPKCS12MacCalculatorBuilder.java new file mode 100644 index 00000000..456d5b08 --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/pkcs/bc/BcPKCS12MacCalculatorBuilder.java @@ -0,0 +1,54 @@ +package org.spongycastle.pkcs.bc; + +import java.security.SecureRandom; + +import org.spongycastle.asn1.DERNull; +import org.spongycastle.asn1.oiw.OIWObjectIdentifiers; +import org.spongycastle.asn1.pkcs.PKCS12PBEParams; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.crypto.ExtendedDigest; +import org.spongycastle.crypto.digests.SHA1Digest; +import org.spongycastle.operator.MacCalculator; +import org.spongycastle.pkcs.PKCS12MacCalculatorBuilder; + +public class BcPKCS12MacCalculatorBuilder + implements PKCS12MacCalculatorBuilder +{ + private ExtendedDigest digest; + private AlgorithmIdentifier algorithmIdentifier; + + private SecureRandom random; + private int saltLength; + private int iterationCount = 1024; + + public BcPKCS12MacCalculatorBuilder() + { + this(new SHA1Digest(), new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, DERNull.INSTANCE)); + } + + public BcPKCS12MacCalculatorBuilder(ExtendedDigest digest, AlgorithmIdentifier algorithmIdentifier) + { + this.digest = digest; + this.algorithmIdentifier = algorithmIdentifier; + this.saltLength = digest.getDigestSize(); + } + + public AlgorithmIdentifier getDigestAlgorithmIdentifier() + { + return algorithmIdentifier; + } + + public MacCalculator build(final char[] password) + { + if (random == null) + { + random = new SecureRandom(); + } + + byte[] salt = new byte[saltLength]; + + random.nextBytes(salt); + + return PKCS12PBEUtils.createMacCalculator(algorithmIdentifier.getAlgorithm(), digest, new PKCS12PBEParams(salt, iterationCount), password); + } +} diff --git a/pkix/src/main/java/org/spongycastle/pkcs/bc/BcPKCS12MacCalculatorBuilderProvider.java b/pkix/src/main/java/org/spongycastle/pkcs/bc/BcPKCS12MacCalculatorBuilderProvider.java new file mode 100644 index 00000000..d5533753 --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/pkcs/bc/BcPKCS12MacCalculatorBuilderProvider.java @@ -0,0 +1,40 @@ +package org.spongycastle.pkcs.bc; + +import org.spongycastle.asn1.DERNull; +import org.spongycastle.asn1.pkcs.PKCS12PBEParams; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.operator.MacCalculator; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.operator.bc.BcDigestProvider; +import org.spongycastle.pkcs.PKCS12MacCalculatorBuilder; +import org.spongycastle.pkcs.PKCS12MacCalculatorBuilderProvider; + +public class BcPKCS12MacCalculatorBuilderProvider + implements PKCS12MacCalculatorBuilderProvider +{ + private BcDigestProvider digestProvider; + + public BcPKCS12MacCalculatorBuilderProvider(BcDigestProvider digestProvider) + { + this.digestProvider = digestProvider; + } + + public PKCS12MacCalculatorBuilder get(final AlgorithmIdentifier algorithmIdentifier) + { + return new PKCS12MacCalculatorBuilder() + { + public MacCalculator build(final char[] password) + throws OperatorCreationException + { + PKCS12PBEParams pbeParams = PKCS12PBEParams.getInstance(algorithmIdentifier.getParameters()); + + return PKCS12PBEUtils.createMacCalculator(algorithmIdentifier.getAlgorithm(), digestProvider.get(algorithmIdentifier), pbeParams, password); + } + + public AlgorithmIdentifier getDigestAlgorithmIdentifier() + { + return new AlgorithmIdentifier(algorithmIdentifier.getAlgorithm(), DERNull.INSTANCE); + } + }; + } +} diff --git a/pkix/src/main/java/org/spongycastle/pkcs/bc/BcPKCS12PBEInputDecryptorProviderBuilder.java b/pkix/src/main/java/org/spongycastle/pkcs/bc/BcPKCS12PBEInputDecryptorProviderBuilder.java new file mode 100644 index 00000000..a4618c04 --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/pkcs/bc/BcPKCS12PBEInputDecryptorProviderBuilder.java @@ -0,0 +1,66 @@ +package org.spongycastle.pkcs.bc; + +import java.io.InputStream; + +import org.spongycastle.asn1.pkcs.PKCS12PBEParams; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.ExtendedDigest; +import org.spongycastle.crypto.digests.SHA1Digest; +import org.spongycastle.crypto.generators.PKCS12ParametersGenerator; +import org.spongycastle.crypto.io.CipherInputStream; +import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher; +import org.spongycastle.operator.GenericKey; +import org.spongycastle.operator.InputDecryptor; +import org.spongycastle.operator.InputDecryptorProvider; + +public class BcPKCS12PBEInputDecryptorProviderBuilder +{ + private ExtendedDigest digest; + + public BcPKCS12PBEInputDecryptorProviderBuilder() + { + this(new SHA1Digest()); + } + + public BcPKCS12PBEInputDecryptorProviderBuilder(ExtendedDigest digest) + { + this.digest = digest; + } + + public InputDecryptorProvider build(final char[] password) + { + return new InputDecryptorProvider() + { + public InputDecryptor get(final AlgorithmIdentifier algorithmIdentifier) + { + final PaddedBufferedBlockCipher engine = PKCS12PBEUtils.getEngine(algorithmIdentifier.getAlgorithm()); + + PKCS12PBEParams pbeParams = PKCS12PBEParams.getInstance(algorithmIdentifier.getParameters()); + + CipherParameters params = PKCS12PBEUtils.createCipherParameters(algorithmIdentifier.getAlgorithm(), digest, engine.getBlockSize(), pbeParams, password); + + engine.init(false, params); + + return new InputDecryptor() + { + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return algorithmIdentifier; + } + + public InputStream getInputStream(InputStream input) + { + return new CipherInputStream(input, engine); + } + + public GenericKey getKey() + { + return new GenericKey(PKCS12ParametersGenerator.PKCS12PasswordToBytes(password)); + } + }; + } + }; + + } +} diff --git a/pkix/src/main/java/org/spongycastle/pkcs/bc/BcPKCS12PBEOutputEncryptorBuilder.java b/pkix/src/main/java/org/spongycastle/pkcs/bc/BcPKCS12PBEOutputEncryptorBuilder.java new file mode 100644 index 00000000..d8af97c3 --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/pkcs/bc/BcPKCS12PBEOutputEncryptorBuilder.java @@ -0,0 +1,77 @@ +package org.spongycastle.pkcs.bc; + +import java.io.OutputStream; +import java.security.SecureRandom; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.pkcs.PKCS12PBEParams; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.crypto.BlockCipher; +import org.spongycastle.crypto.BufferedBlockCipher; +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.ExtendedDigest; +import org.spongycastle.crypto.digests.SHA1Digest; +import org.spongycastle.crypto.generators.PKCS12ParametersGenerator; +import org.spongycastle.crypto.io.CipherOutputStream; +import org.spongycastle.crypto.paddings.PKCS7Padding; +import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher; +import org.spongycastle.operator.GenericKey; +import org.spongycastle.operator.OutputEncryptor; + +public class BcPKCS12PBEOutputEncryptorBuilder +{ + private ExtendedDigest digest; + + private BufferedBlockCipher engine; + private ASN1ObjectIdentifier algorithm; + private SecureRandom random; + + public BcPKCS12PBEOutputEncryptorBuilder(ASN1ObjectIdentifier algorithm, BlockCipher engine) + { + this(algorithm, engine, new SHA1Digest()); + } + + public BcPKCS12PBEOutputEncryptorBuilder(ASN1ObjectIdentifier algorithm, BlockCipher engine, ExtendedDigest pbeDigest) + { + this.algorithm = algorithm; + this.engine = new PaddedBufferedBlockCipher(engine, new PKCS7Padding()); + this.digest = pbeDigest; + } + + public OutputEncryptor build(final char[] password) + { + if (random == null) + { + random = new SecureRandom(); + } + + final byte[] salt = new byte[20]; + final int iterationCount = 1024; + + random.nextBytes(salt); + + final PKCS12PBEParams pbeParams = new PKCS12PBEParams(salt, iterationCount); + + CipherParameters params = PKCS12PBEUtils.createCipherParameters(algorithm, digest, engine.getBlockSize(), pbeParams, password); + + engine.init(true, params); + + return new OutputEncryptor() + { + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return new AlgorithmIdentifier(algorithm, pbeParams); + } + + public OutputStream getOutputStream(OutputStream out) + { + return new CipherOutputStream(out, engine); + } + + public GenericKey getKey() + { + return new GenericKey(new AlgorithmIdentifier(algorithm, pbeParams), PKCS12ParametersGenerator.PKCS12PasswordToBytes(password)); + } + }; + } +} diff --git a/pkix/src/main/java/org/spongycastle/pkcs/bc/PKCS12PBEUtils.java b/pkix/src/main/java/org/spongycastle/pkcs/bc/PKCS12PBEUtils.java new file mode 100644 index 00000000..52f07a54 --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/pkcs/bc/PKCS12PBEUtils.java @@ -0,0 +1,153 @@ +package org.spongycastle.pkcs.bc; + +import java.io.OutputStream; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.pkcs.PKCS12PBEParams; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.crypto.BlockCipher; +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.ExtendedDigest; +import org.spongycastle.crypto.engines.DESedeEngine; +import org.spongycastle.crypto.engines.RC2Engine; +import org.spongycastle.crypto.generators.PKCS12ParametersGenerator; +import org.spongycastle.crypto.io.MacOutputStream; +import org.spongycastle.crypto.macs.HMac; +import org.spongycastle.crypto.modes.CBCBlockCipher; +import org.spongycastle.crypto.paddings.PKCS7Padding; +import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher; +import org.spongycastle.crypto.params.DESedeParameters; +import org.spongycastle.crypto.params.KeyParameter; +import org.spongycastle.crypto.params.ParametersWithIV; +import org.spongycastle.operator.GenericKey; +import org.spongycastle.operator.MacCalculator; +import org.spongycastle.util.Integers; + +class PKCS12PBEUtils +{ + private static Map keySizes = new HashMap(); + private static Set noIvAlgs = new HashSet(); + private static Set desAlgs = new HashSet(); + + static + { + keySizes.put(PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC4, Integers.valueOf(128)); + keySizes.put(PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC4, Integers.valueOf(40)); + keySizes.put(PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC, Integers.valueOf(192)); + keySizes.put(PKCSObjectIdentifiers.pbeWithSHAAnd2_KeyTripleDES_CBC, Integers.valueOf(128)); + keySizes.put(PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC2_CBC, Integers.valueOf(128)); + keySizes.put(PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC2_CBC, Integers.valueOf(40)); + + noIvAlgs.add(PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC4); + noIvAlgs.add(PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC4); + + desAlgs.add(PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC); + desAlgs.add(PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC); + } + + static int getKeySize(ASN1ObjectIdentifier algorithm) + { + return ((Integer)keySizes.get(algorithm)).intValue(); + } + + static boolean hasNoIv(ASN1ObjectIdentifier algorithm) + { + return noIvAlgs.contains(algorithm); + } + + static boolean isDesAlg(ASN1ObjectIdentifier algorithm) + { + return desAlgs.contains(algorithm); + } + + static PaddedBufferedBlockCipher getEngine(ASN1ObjectIdentifier algorithm) + { + BlockCipher engine; + + if (algorithm.equals(PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC) + || algorithm.equals(PKCSObjectIdentifiers.pbeWithSHAAnd2_KeyTripleDES_CBC)) + { + engine = new DESedeEngine(); + } + else if (algorithm.equals(PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC2_CBC) + || algorithm.equals(PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC2_CBC)) + { + engine = new RC2Engine(); + } + else + { + throw new IllegalStateException("unknown algorithm"); + } + + return new PaddedBufferedBlockCipher(new CBCBlockCipher(engine), new PKCS7Padding()); + } + + static MacCalculator createMacCalculator(final ASN1ObjectIdentifier digestAlgorithm, ExtendedDigest digest, final PKCS12PBEParams pbeParams, final char[] password) + { + PKCS12ParametersGenerator pGen = new PKCS12ParametersGenerator(digest); + + pGen.init(PKCS12ParametersGenerator.PKCS12PasswordToBytes(password), pbeParams.getIV(), pbeParams.getIterations().intValue()); + + final KeyParameter keyParam = (KeyParameter)pGen.generateDerivedMacParameters(digest.getDigestSize() * 8); + + final HMac hMac = new HMac(digest); + + hMac.init(keyParam); + + return new MacCalculator() + { + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return new AlgorithmIdentifier(digestAlgorithm, pbeParams); + } + + public OutputStream getOutputStream() + { + return new MacOutputStream(hMac); + } + + public byte[] getMac() + { + byte[] res = new byte[hMac.getMacSize()]; + + hMac.doFinal(res, 0); + + return res; + } + + public GenericKey getKey() + { + return new GenericKey(getAlgorithmIdentifier(), PKCS12ParametersGenerator.PKCS12PasswordToBytes(password)); + } + }; + } + + static CipherParameters createCipherParameters(ASN1ObjectIdentifier algorithm, ExtendedDigest digest, int blockSize, PKCS12PBEParams pbeParams, char[] password) + { + PKCS12ParametersGenerator pGen = new PKCS12ParametersGenerator(digest); + + pGen.init(PKCS12ParametersGenerator.PKCS12PasswordToBytes(password), pbeParams.getIV(), pbeParams.getIterations().intValue()); + + CipherParameters params; + + if (PKCS12PBEUtils.hasNoIv(algorithm)) + { + params = pGen.generateDerivedParameters(PKCS12PBEUtils.getKeySize(algorithm)); + } + else + { + params = pGen.generateDerivedParameters(PKCS12PBEUtils.getKeySize(algorithm), blockSize * 8); + + if (PKCS12PBEUtils.isDesAlg(algorithm)) + { + DESedeParameters.setOddParity(((KeyParameter)((ParametersWithIV)params).getParameters()).getKey()); + } + } + return params; + } +} diff --git a/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcaPKCS10CertificationRequest.java b/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcaPKCS10CertificationRequest.java new file mode 100644 index 00000000..8f0e20cf --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcaPKCS10CertificationRequest.java @@ -0,0 +1,115 @@ +package org.spongycastle.pkcs.jcajce; + +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Provider; +import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.Hashtable; + +import org.spongycastle.asn1.pkcs.CertificationRequest; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.asn1.x9.X9ObjectIdentifiers; +import org.spongycastle.jcajce.util.DefaultJcaJceHelper; +import org.spongycastle.jcajce.util.JcaJceHelper; +import org.spongycastle.jcajce.util.NamedJcaJceHelper; +import org.spongycastle.jcajce.util.ProviderJcaJceHelper; +import org.spongycastle.pkcs.PKCS10CertificationRequest; + +public class JcaPKCS10CertificationRequest + extends PKCS10CertificationRequest +{ + private static Hashtable keyAlgorithms = new Hashtable(); + + static + { + // + // key types + // + keyAlgorithms.put(PKCSObjectIdentifiers.rsaEncryption, "RSA"); + keyAlgorithms.put(X9ObjectIdentifiers.id_dsa, "DSA"); + } + + private JcaJceHelper helper = new DefaultJcaJceHelper(); + + public JcaPKCS10CertificationRequest(CertificationRequest certificationRequest) + { + super(certificationRequest); + } + + public JcaPKCS10CertificationRequest(byte[] encoding) + throws IOException + { + super(encoding); + } + + public JcaPKCS10CertificationRequest(PKCS10CertificationRequest requestHolder) + { + super(requestHolder.toASN1Structure()); + } + + public JcaPKCS10CertificationRequest setProvider(String providerName) + { + helper = new NamedJcaJceHelper(providerName); + + return this; + } + + public JcaPKCS10CertificationRequest setProvider(Provider provider) + { + helper = new ProviderJcaJceHelper(provider); + + return this; + } + + public PublicKey getPublicKey() + throws InvalidKeyException, NoSuchAlgorithmException + { + try + { + SubjectPublicKeyInfo keyInfo = this.getSubjectPublicKeyInfo(); + X509EncodedKeySpec xspec = new X509EncodedKeySpec(keyInfo.getEncoded()); + KeyFactory kFact; + + try + { + kFact = helper.createKeyFactory(keyInfo.getAlgorithm().getAlgorithm().getId()); + } + catch (NoSuchAlgorithmException e) + { + // + // try an alternate + // + if (keyAlgorithms.get(keyInfo.getAlgorithm().getAlgorithm()) != null) + { + String keyAlgorithm = (String)keyAlgorithms.get(keyInfo.getAlgorithm().getAlgorithm()); + + kFact = helper.createKeyFactory(keyAlgorithm); + } + else + { + throw e; + } + } + + return kFact.generatePublic(xspec); + } + catch (InvalidKeySpecException e) + { + throw new InvalidKeyException("error decoding public key"); + } + catch (IOException e) + { + throw new InvalidKeyException("error extracting key encoding"); + } + catch (NoSuchProviderException e) + { + throw new NoSuchAlgorithmException("cannot find provider: " + e.getMessage()); + } + } +} diff --git a/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcaPKCS10CertificationRequestBuilder.java b/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcaPKCS10CertificationRequestBuilder.java new file mode 100644 index 00000000..0efa5fa2 --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcaPKCS10CertificationRequestBuilder.java @@ -0,0 +1,38 @@ +package org.spongycastle.pkcs.jcajce; + +import java.security.PublicKey; + +import javax.security.auth.x500.X500Principal; + +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.pkcs.PKCS10CertificationRequestBuilder; + +/** + * Extension of the PKCS#10 builder to support PublicKey and X500Principal objects. + */ +public class JcaPKCS10CertificationRequestBuilder + extends PKCS10CertificationRequestBuilder +{ + /** + * Create a PKCS#10 builder for the passed in subject and JCA public key. + * + * @param subject an X500Name containing the subject associated with the request we are building. + * @param publicKey a JCA public key that is to be associated with the request we are building. + */ + public JcaPKCS10CertificationRequestBuilder(X500Name subject, PublicKey publicKey) + { + super(subject, SubjectPublicKeyInfo.getInstance(publicKey.getEncoded())); + } + + /** + * Create a PKCS#10 builder for the passed in subject and JCA public key. + * + * @param subject an X500Principal containing the subject associated with the request we are building. + * @param publicKey a JCA public key that is to be associated with the request we are building. + */ + public JcaPKCS10CertificationRequestBuilder(X500Principal subject, PublicKey publicKey) + { + super(X500Name.getInstance(subject.getEncoded()), SubjectPublicKeyInfo.getInstance(publicKey.getEncoded())); + } +} diff --git a/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcaPKCS12SafeBagBuilder.java b/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcaPKCS12SafeBagBuilder.java new file mode 100644 index 00000000..f8c06f7c --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcaPKCS12SafeBagBuilder.java @@ -0,0 +1,45 @@ +package org.spongycastle.pkcs.jcajce; + +import java.io.IOException; +import java.security.PrivateKey; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; + +import org.spongycastle.asn1.pkcs.PrivateKeyInfo; +import org.spongycastle.asn1.x509.Certificate; +import org.spongycastle.operator.OutputEncryptor; +import org.spongycastle.pkcs.PKCS12SafeBagBuilder; +import org.spongycastle.pkcs.PKCSIOException; + +public class JcaPKCS12SafeBagBuilder + extends PKCS12SafeBagBuilder +{ + public JcaPKCS12SafeBagBuilder(X509Certificate certificate) + throws IOException + { + super(convertCert(certificate)); + } + + private static Certificate convertCert(X509Certificate certificate) + throws IOException + { + try + { + return Certificate.getInstance(certificate.getEncoded()); + } + catch (CertificateEncodingException e) + { + throw new PKCSIOException("cannot encode certificate: " + e.getMessage(), e); + } + } + + public JcaPKCS12SafeBagBuilder(PrivateKey privateKey, OutputEncryptor encryptor) + { + super(PrivateKeyInfo.getInstance(privateKey.getEncoded()), encryptor); + } + + public JcaPKCS12SafeBagBuilder(PrivateKey privateKey) + { + super(PrivateKeyInfo.getInstance(privateKey.getEncoded())); + } +} diff --git a/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcaPKCS8EncryptedPrivateKeyInfoBuilder.java b/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcaPKCS8EncryptedPrivateKeyInfoBuilder.java new file mode 100644 index 00000000..f8a5856c --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcaPKCS8EncryptedPrivateKeyInfoBuilder.java @@ -0,0 +1,15 @@ +package org.spongycastle.pkcs.jcajce; + +import java.security.PrivateKey; + +import org.spongycastle.asn1.pkcs.PrivateKeyInfo; +import org.spongycastle.pkcs.PKCS8EncryptedPrivateKeyInfoBuilder; + +public class JcaPKCS8EncryptedPrivateKeyInfoBuilder + extends PKCS8EncryptedPrivateKeyInfoBuilder +{ + public JcaPKCS8EncryptedPrivateKeyInfoBuilder(PrivateKey privateKey) + { + super(PrivateKeyInfo.getInstance(privateKey.getEncoded())); + } +} diff --git a/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcePKCS12MacCalculatorBuilder.java b/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcePKCS12MacCalculatorBuilder.java new file mode 100644 index 00000000..25b8da06 --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcePKCS12MacCalculatorBuilder.java @@ -0,0 +1,122 @@ +package org.spongycastle.pkcs.jcajce; + +import java.io.OutputStream; +import java.security.Provider; +import java.security.SecureRandom; + +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.PBEParameterSpec; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.DERNull; +import org.spongycastle.asn1.oiw.OIWObjectIdentifiers; +import org.spongycastle.asn1.pkcs.PKCS12PBEParams; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.crypto.ExtendedDigest; +import org.spongycastle.crypto.generators.PKCS12ParametersGenerator; +import org.spongycastle.jcajce.io.MacOutputStream; +import org.spongycastle.jcajce.util.DefaultJcaJceHelper; +import org.spongycastle.jcajce.util.JcaJceHelper; +import org.spongycastle.jcajce.util.NamedJcaJceHelper; +import org.spongycastle.jcajce.util.ProviderJcaJceHelper; +import org.spongycastle.operator.GenericKey; +import org.spongycastle.operator.MacCalculator; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.pkcs.PKCS12MacCalculatorBuilder; + +public class JcePKCS12MacCalculatorBuilder + implements PKCS12MacCalculatorBuilder +{ + private JcaJceHelper helper = new DefaultJcaJceHelper(); + private ExtendedDigest digest; + private ASN1ObjectIdentifier algorithm; + + private SecureRandom random; + private int saltLength; + private int iterationCount = 1024; + + public JcePKCS12MacCalculatorBuilder() + { + this(OIWObjectIdentifiers.idSHA1); + } + + public JcePKCS12MacCalculatorBuilder(ASN1ObjectIdentifier hashAlgorithm) + { + this.algorithm = hashAlgorithm; + } + + public JcePKCS12MacCalculatorBuilder setProvider(Provider provider) + { + this.helper = new ProviderJcaJceHelper(provider); + + return this; + } + + public JcePKCS12MacCalculatorBuilder setProvider(String providerName) + { + this.helper = new NamedJcaJceHelper(providerName); + + return this; + } + + public AlgorithmIdentifier getDigestAlgorithmIdentifier() + { + return new AlgorithmIdentifier(algorithm, DERNull.INSTANCE); + } + + public MacCalculator build(final char[] password) + throws OperatorCreationException + { + if (random == null) + { + random = new SecureRandom(); + } + + try + { + final Mac mac = helper.createMac(algorithm.getId()); + + saltLength = mac.getMacLength(); + final byte[] salt = new byte[saltLength]; + + random.nextBytes(salt); + + SecretKeyFactory keyFact = helper.createSecretKeyFactory(algorithm.getId()); + PBEParameterSpec defParams = new PBEParameterSpec(salt, iterationCount); + PBEKeySpec pbeSpec = new PBEKeySpec(password); + SecretKey key = keyFact.generateSecret(pbeSpec); + + mac.init(key, defParams); + + return new MacCalculator() + { + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return new AlgorithmIdentifier(algorithm, new PKCS12PBEParams(salt, iterationCount)); + } + + public OutputStream getOutputStream() + { + return new MacOutputStream(mac); + } + + public byte[] getMac() + { + return mac.doFinal(); + } + + public GenericKey getKey() + { + return new GenericKey(getAlgorithmIdentifier(), PKCS12ParametersGenerator.PKCS12PasswordToBytes(password)); + } + }; + } + catch (Exception e) + { + throw new OperatorCreationException("unable to create MAC calculator: " + e.getMessage(), e); + } + } +} diff --git a/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcePKCS12MacCalculatorBuilderProvider.java b/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcePKCS12MacCalculatorBuilderProvider.java new file mode 100644 index 00000000..6c9c3023 --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcePKCS12MacCalculatorBuilderProvider.java @@ -0,0 +1,108 @@ +package org.spongycastle.pkcs.jcajce; + +import java.io.OutputStream; +import java.security.Provider; + +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.PBEParameterSpec; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.DERNull; +import org.spongycastle.asn1.pkcs.PKCS12PBEParams; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.crypto.generators.PKCS12ParametersGenerator; +import org.spongycastle.jcajce.io.MacOutputStream; +import org.spongycastle.jcajce.util.DefaultJcaJceHelper; +import org.spongycastle.jcajce.util.JcaJceHelper; +import org.spongycastle.jcajce.util.NamedJcaJceHelper; +import org.spongycastle.jcajce.util.ProviderJcaJceHelper; +import org.spongycastle.operator.GenericKey; +import org.spongycastle.operator.MacCalculator; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.pkcs.PKCS12MacCalculatorBuilder; +import org.spongycastle.pkcs.PKCS12MacCalculatorBuilderProvider; + +public class JcePKCS12MacCalculatorBuilderProvider + implements PKCS12MacCalculatorBuilderProvider +{ + private JcaJceHelper helper = new DefaultJcaJceHelper(); + + public JcePKCS12MacCalculatorBuilderProvider() + { + } + + public JcePKCS12MacCalculatorBuilderProvider setProvider(Provider provider) + { + this.helper = new ProviderJcaJceHelper(provider); + + return this; + } + + public JcePKCS12MacCalculatorBuilderProvider setProvider(String providerName) + { + this.helper = new NamedJcaJceHelper(providerName); + + return this; + } + + public PKCS12MacCalculatorBuilder get(final AlgorithmIdentifier algorithmIdentifier) + { + return new PKCS12MacCalculatorBuilder() + { + public MacCalculator build(final char[] password) + throws OperatorCreationException + { + final PKCS12PBEParams pbeParams = PKCS12PBEParams.getInstance(algorithmIdentifier.getParameters()); + + try + { + final ASN1ObjectIdentifier algorithm = algorithmIdentifier.getAlgorithm(); + + final Mac mac = helper.createMac(algorithm.getId()); + + SecretKeyFactory keyFact = helper.createSecretKeyFactory(algorithm.getId()); + PBEParameterSpec defParams = new PBEParameterSpec(pbeParams.getIV(), pbeParams.getIterations().intValue()); + PBEKeySpec pbeSpec = new PBEKeySpec(password); + SecretKey key = keyFact.generateSecret(pbeSpec); + + mac.init(key, defParams); + + return new MacCalculator() + { + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return new AlgorithmIdentifier(algorithm, pbeParams); + } + + public OutputStream getOutputStream() + { + return new MacOutputStream(mac); + } + + public byte[] getMac() + { + return mac.doFinal(); + } + + public GenericKey getKey() + { + return new GenericKey(getAlgorithmIdentifier(), PKCS12ParametersGenerator.PKCS12PasswordToBytes(password)); + } + }; + } + catch (Exception e) + { + throw new OperatorCreationException("unable to create MAC calculator: " + e.getMessage(), e); + } + } + + public AlgorithmIdentifier getDigestAlgorithmIdentifier() + { + return new AlgorithmIdentifier(algorithmIdentifier.getAlgorithm(), DERNull.INSTANCE); + } + }; + } +} diff --git a/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcePKCSPBEInputDecryptorProviderBuilder.java b/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcePKCSPBEInputDecryptorProviderBuilder.java new file mode 100644 index 00000000..5f5413ca --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcePKCSPBEInputDecryptorProviderBuilder.java @@ -0,0 +1,177 @@ +package org.spongycastle.pkcs.jcajce; + +import java.io.InputStream; +import java.security.Provider; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.PBEParameterSpec; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.cryptopro.GOST28147Parameters; +import org.spongycastle.asn1.pkcs.PBES2Parameters; +import org.spongycastle.asn1.pkcs.PBKDF2Params; +import org.spongycastle.asn1.pkcs.PKCS12PBEParams; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.jcajce.provider.symmetric.util.BCPBEKey; +import org.spongycastle.jcajce.spec.GOST28147ParameterSpec; +import org.spongycastle.jcajce.spec.PBKDF2KeySpec; +import org.spongycastle.jcajce.util.DefaultJcaJceHelper; +import org.spongycastle.jcajce.util.JcaJceHelper; +import org.spongycastle.jcajce.util.NamedJcaJceHelper; +import org.spongycastle.jcajce.util.ProviderJcaJceHelper; +import org.spongycastle.operator.DefaultSecretKeySizeProvider; +import org.spongycastle.operator.InputDecryptor; +import org.spongycastle.operator.InputDecryptorProvider; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.operator.SecretKeySizeProvider; + +public class JcePKCSPBEInputDecryptorProviderBuilder +{ + private JcaJceHelper helper = new DefaultJcaJceHelper(); + private boolean wrongPKCS12Zero = false; + private SecretKeySizeProvider keySizeProvider = DefaultSecretKeySizeProvider.INSTANCE; + + public JcePKCSPBEInputDecryptorProviderBuilder() + { + } + + public JcePKCSPBEInputDecryptorProviderBuilder setProvider(Provider provider) + { + this.helper = new ProviderJcaJceHelper(provider); + + return this; + } + + public JcePKCSPBEInputDecryptorProviderBuilder setProvider(String providerName) + { + this.helper = new NamedJcaJceHelper(providerName); + + return this; + } + + public JcePKCSPBEInputDecryptorProviderBuilder setTryWrongPKCS12Zero(boolean tryWrong) + { + this.wrongPKCS12Zero = tryWrong; + + return this; + } + + /** + * Set the lookup provider of AlgorithmIdentifier returning key_size_in_bits used to + * handle PKCS5 decryption. + * + * @param keySizeProvider a provider of integer secret key sizes. + * + * @return the current builder. + */ + public JcePKCSPBEInputDecryptorProviderBuilder setKeySizeProvider(SecretKeySizeProvider keySizeProvider) + { + this.keySizeProvider = keySizeProvider; + + return this; + } + + public InputDecryptorProvider build(final char[] password) + { + return new InputDecryptorProvider() + { + private Cipher cipher; + private SecretKey key; + private AlgorithmIdentifier encryptionAlg; + + public InputDecryptor get(final AlgorithmIdentifier algorithmIdentifier) + throws OperatorCreationException + { + ASN1ObjectIdentifier algorithm = algorithmIdentifier.getAlgorithm(); + + try + { + if (algorithm.on(PKCSObjectIdentifiers.pkcs_12PbeIds)) + { + PKCS12PBEParams pbeParams = PKCS12PBEParams.getInstance(algorithmIdentifier.getParameters()); + + PBEKeySpec pbeSpec = new PBEKeySpec(password); + + SecretKeyFactory keyFact = helper.createSecretKeyFactory(algorithm.getId()); + + PBEParameterSpec defParams = new PBEParameterSpec( + pbeParams.getIV(), + pbeParams.getIterations().intValue()); + + key = keyFact.generateSecret(pbeSpec); + + if (key instanceof BCPBEKey) + { + ((BCPBEKey)key).setTryWrongPKCS12Zero(wrongPKCS12Zero); + } + + cipher = helper.createCipher(algorithm.getId()); + + cipher.init(Cipher.DECRYPT_MODE, key, defParams); + + encryptionAlg = algorithmIdentifier; + } + else if (algorithm.equals(PKCSObjectIdentifiers.id_PBES2)) + { + PBES2Parameters alg = PBES2Parameters.getInstance(algorithmIdentifier.getParameters()); + PBKDF2Params func = PBKDF2Params.getInstance(alg.getKeyDerivationFunc().getParameters()); + AlgorithmIdentifier encScheme = AlgorithmIdentifier.getInstance(alg.getEncryptionScheme()); + + SecretKeyFactory keyFact = helper.createSecretKeyFactory(alg.getKeyDerivationFunc().getAlgorithm().getId()); + + if (func.isDefaultPrf()) + { + key = keyFact.generateSecret(new PBEKeySpec(password, func.getSalt(), func.getIterationCount().intValue(), keySizeProvider.getKeySize(encScheme))); + } + else + { + key = keyFact.generateSecret(new PBKDF2KeySpec(password, func.getSalt(), func.getIterationCount().intValue(), keySizeProvider.getKeySize(encScheme), func.getPrf())); + } + + cipher = helper.createCipher(alg.getEncryptionScheme().getAlgorithm().getId()); + + encryptionAlg = AlgorithmIdentifier.getInstance(alg.getEncryptionScheme()); + + ASN1Encodable encParams = alg.getEncryptionScheme().getParameters(); + if (encParams instanceof ASN1OctetString) + { + cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(ASN1OctetString.getInstance(encParams).getOctets())); + } + else + { + // TODO: at the moment it's just GOST, but... + GOST28147Parameters gParams = GOST28147Parameters.getInstance(encParams); + + cipher.init(Cipher.DECRYPT_MODE, key, new GOST28147ParameterSpec(gParams.getEncryptionParamSet(), gParams.getIV())); + } + } + } + catch (Exception e) + { + throw new OperatorCreationException("unable to create InputDecryptor: " + e.getMessage(), e); + } + + return new InputDecryptor() + { + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return encryptionAlg; + } + + public InputStream getInputStream(InputStream input) + { + return new CipherInputStream(input, cipher); + } + }; + } + }; + } +} diff --git a/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcePKCSPBEOutputEncryptorBuilder.java b/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcePKCSPBEOutputEncryptorBuilder.java new file mode 100644 index 00000000..9ab806ef --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcePKCSPBEOutputEncryptorBuilder.java @@ -0,0 +1,179 @@ +package org.spongycastle.pkcs.jcajce; + +import java.io.OutputStream; +import java.security.Provider; +import java.security.SecureRandom; + +import javax.crypto.Cipher; +import javax.crypto.CipherOutputStream; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.PBEParameterSpec; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.bc.BCObjectIdentifiers; +import org.spongycastle.asn1.pkcs.EncryptionScheme; +import org.spongycastle.asn1.pkcs.KeyDerivationFunc; +import org.spongycastle.asn1.pkcs.PBES2Parameters; +import org.spongycastle.asn1.pkcs.PBKDF2Params; +import org.spongycastle.asn1.pkcs.PKCS12PBEParams; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.crypto.PBEParametersGenerator; +import org.spongycastle.jcajce.util.DefaultJcaJceHelper; +import org.spongycastle.jcajce.util.JcaJceHelper; +import org.spongycastle.jcajce.util.NamedJcaJceHelper; +import org.spongycastle.jcajce.util.ProviderJcaJceHelper; +import org.spongycastle.operator.DefaultSecretKeySizeProvider; +import org.spongycastle.operator.GenericKey; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.operator.OutputEncryptor; +import org.spongycastle.operator.SecretKeySizeProvider; + +public class JcePKCSPBEOutputEncryptorBuilder +{ + private JcaJceHelper helper = new DefaultJcaJceHelper(); + private ASN1ObjectIdentifier algorithm; + private ASN1ObjectIdentifier keyEncAlgorithm; + private SecureRandom random; + private SecretKeySizeProvider keySizeProvider = DefaultSecretKeySizeProvider.INSTANCE; + + public JcePKCSPBEOutputEncryptorBuilder(ASN1ObjectIdentifier algorithm) + { + if (isPKCS12(algorithm)) + { + this.algorithm = algorithm; + this.keyEncAlgorithm = algorithm; + } + else + { + this.algorithm = PKCSObjectIdentifiers.id_PBES2; + this.keyEncAlgorithm = algorithm; + } + } + + public JcePKCSPBEOutputEncryptorBuilder setProvider(Provider provider) + { + this.helper = new ProviderJcaJceHelper(provider); + + return this; + } + + public JcePKCSPBEOutputEncryptorBuilder setProvider(String providerName) + { + this.helper = new NamedJcaJceHelper(providerName); + + return this; + } + + /** + * Set the lookup provider of AlgorithmIdentifier returning key_size_in_bits used to + * handle PKCS5 decryption. + * + * @param keySizeProvider a provider of integer secret key sizes. + * + * @return the current builder. + */ + public JcePKCSPBEOutputEncryptorBuilder setKeySizeProvider(SecretKeySizeProvider keySizeProvider) + { + this.keySizeProvider = keySizeProvider; + + return this; + } + + public OutputEncryptor build(final char[] password) + throws OperatorCreationException + { + final Cipher cipher; + SecretKey key; + + if (random == null) + { + random = new SecureRandom(); + } + + final AlgorithmIdentifier encryptionAlg; + final byte[] salt = new byte[20]; + final int iterationCount = 1024; + + random.nextBytes(salt); + + try + { + if (algorithm.on(PKCSObjectIdentifiers.pkcs_12PbeIds)) + { + PBEKeySpec pbeSpec = new PBEKeySpec(password); + + SecretKeyFactory keyFact = helper.createSecretKeyFactory(algorithm.getId()); + + PBEParameterSpec defParams = new PBEParameterSpec(salt, iterationCount); + + key = keyFact.generateSecret(pbeSpec); + + cipher = helper.createCipher(algorithm.getId()); + + cipher.init(Cipher.ENCRYPT_MODE, key, defParams); + + encryptionAlg = new AlgorithmIdentifier(algorithm, new PKCS12PBEParams(salt, iterationCount)); + } + else if (algorithm.equals(PKCSObjectIdentifiers.id_PBES2)) + { + SecretKeyFactory keyFact = helper.createSecretKeyFactory(PKCSObjectIdentifiers.id_PBKDF2.getId()); + + key = keyFact.generateSecret(new PBEKeySpec(password, salt, iterationCount, keySizeProvider.getKeySize(new AlgorithmIdentifier(keyEncAlgorithm)))); + + cipher = helper.createCipher(keyEncAlgorithm.getId()); + + cipher.init(Cipher.ENCRYPT_MODE, key, random); + + PBES2Parameters algParams = new PBES2Parameters( + new KeyDerivationFunc(PKCSObjectIdentifiers.id_PBKDF2, new PBKDF2Params(salt, iterationCount)), + new EncryptionScheme(keyEncAlgorithm, ASN1Primitive.fromByteArray(cipher.getParameters().getEncoded()))); + + encryptionAlg = new AlgorithmIdentifier(algorithm, algParams); + } + else + { + throw new OperatorCreationException("unrecognised algorithm"); + } + + return new OutputEncryptor() + { + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return encryptionAlg; + } + + public OutputStream getOutputStream(OutputStream out) + { + return new CipherOutputStream(out, cipher); + } + + public GenericKey getKey() + { + if (isPKCS12(encryptionAlg.getAlgorithm())) + { + return new GenericKey(encryptionAlg, PBEParametersGenerator.PKCS5PasswordToBytes(password)); + } + else + { + return new GenericKey(encryptionAlg, PBEParametersGenerator.PKCS12PasswordToBytes(password)); + } + } + }; + } + catch (Exception e) + { + throw new OperatorCreationException("unable to create OutputEncryptor: " + e.getMessage(), e); + } + } + + private boolean isPKCS12(ASN1ObjectIdentifier algorithm) + { + return algorithm.on(PKCSObjectIdentifiers.pkcs_12PbeIds) + || algorithm.on(BCObjectIdentifiers.bc_pbe_sha1_pkcs12) + || algorithm.on(BCObjectIdentifiers.bc_pbe_sha256_pkcs12); + } +} |