diff options
Diffstat (limited to 'libraries/spongycastle/pkix/src/main/java/org')
434 files changed, 39702 insertions, 0 deletions
diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/AttributeCertificateHolder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/AttributeCertificateHolder.java new file mode 100644 index 000000000..610cdbe73 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/AttributeCertificateHolder.java @@ -0,0 +1,357 @@ +package org.spongycastle.cert; + +import java.io.OutputStream; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.GeneralName; +import org.spongycastle.asn1.x509.GeneralNames; +import org.spongycastle.asn1.x509.Holder; +import org.spongycastle.asn1.x509.IssuerSerial; +import org.spongycastle.asn1.x509.ObjectDigestInfo; +import org.spongycastle.operator.DigestCalculator; +import org.spongycastle.operator.DigestCalculatorProvider; +import org.spongycastle.util.Arrays; +import org.spongycastle.util.Selector; + +/** + * The Holder object. + * + * <pre> + * Holder ::= SEQUENCE { + * baseCertificateID [0] IssuerSerial OPTIONAL, + * -- the issuer and serial number of + * -- the holder's Public Key Certificate + * entityName [1] GeneralNames OPTIONAL, + * -- the name of the claimant or role + * objectDigestInfo [2] ObjectDigestInfo OPTIONAL + * -- used to directly authenticate the holder, + * -- for example, an executable + * } + * </pre> + * <p> + * <b>Note:</b> If objectDigestInfo comparisons are to be carried out the static + * method setDigestCalculatorProvider <b>must</b> be called once to configure the class + * to do the necessary calculations. + * </p> + */ +public class AttributeCertificateHolder + implements Selector +{ + private static DigestCalculatorProvider digestCalculatorProvider; + + final Holder holder; + + AttributeCertificateHolder(ASN1Sequence seq) + { + holder = Holder.getInstance(seq); + } + + public AttributeCertificateHolder(X500Name issuerName, + BigInteger serialNumber) + { + holder = new Holder(new IssuerSerial( + new GeneralNames(new GeneralName(issuerName)), + new ASN1Integer(serialNumber))); + } + + public AttributeCertificateHolder(X509CertificateHolder cert) + { + holder = new Holder(new IssuerSerial(generateGeneralNames(cert.getIssuer()), + new ASN1Integer(cert.getSerialNumber()))); + } + + public AttributeCertificateHolder(X500Name principal) + { + holder = new Holder(generateGeneralNames(principal)); + } + + /** + * Constructs a holder for v2 attribute certificates with a hash value for + * some type of object. + * <p> + * <code>digestedObjectType</code> can be one of the following: + * <ul> + * <li>0 - publicKey - A hash of the public key of the holder must be + * passed. + * <li>1 - publicKeyCert - A hash of the public key certificate of the + * holder must be passed. + * <li>2 - otherObjectDigest - A hash of some other object type must be + * passed. <code>otherObjectTypeID</code> must not be empty. + * </ul> + * <p> + * This cannot be used if a v1 attribute certificate is used. + * + * @param digestedObjectType The digest object type. + * @param digestAlgorithm The algorithm identifier for the hash. + * @param otherObjectTypeID The object type ID if + * <code>digestedObjectType</code> is + * <code>otherObjectDigest</code>. + * @param objectDigest The hash value. + */ + public AttributeCertificateHolder(int digestedObjectType, + ASN1ObjectIdentifier digestAlgorithm, ASN1ObjectIdentifier otherObjectTypeID, byte[] objectDigest) + { + holder = new Holder(new ObjectDigestInfo(digestedObjectType, + otherObjectTypeID, new AlgorithmIdentifier(digestAlgorithm), Arrays + .clone(objectDigest))); + } + + /** + * Returns the digest object type if an object digest info is used. + * <p> + * <ul> + * <li>0 - publicKey - A hash of the public key of the holder must be + * passed. + * <li>1 - publicKeyCert - A hash of the public key certificate of the + * holder must be passed. + * <li>2 - otherObjectDigest - A hash of some other object type must be + * passed. <code>otherObjectTypeID</code> must not be empty. + * </ul> + * + * @return The digest object type or -1 if no object digest info is set. + */ + public int getDigestedObjectType() + { + if (holder.getObjectDigestInfo() != null) + { + return holder.getObjectDigestInfo().getDigestedObjectType() + .getValue().intValue(); + } + return -1; + } + + /** + * Returns algorithm identifier for the digest used if ObjectDigestInfo is present. + * + * @return digest AlgorithmIdentifier or <code>null</code> if ObjectDigestInfo is absent. + */ + public AlgorithmIdentifier getDigestAlgorithm() + { + if (holder.getObjectDigestInfo() != null) + { + return holder.getObjectDigestInfo().getDigestAlgorithm(); + } + return null; + } + + /** + * Returns the hash if an object digest info is used. + * + * @return The hash or <code>null</code> if ObjectDigestInfo is absent. + */ + public byte[] getObjectDigest() + { + if (holder.getObjectDigestInfo() != null) + { + return holder.getObjectDigestInfo().getObjectDigest().getBytes(); + } + return null; + } + + /** + * Returns the digest algorithm ID if an object digest info is used. + * + * @return The digest algorithm ID or <code>null</code> if no object + * digest info is set. + */ + public ASN1ObjectIdentifier getOtherObjectTypeID() + { + if (holder.getObjectDigestInfo() != null) + { + new ASN1ObjectIdentifier(holder.getObjectDigestInfo().getOtherObjectTypeID().getId()); + } + return null; + } + + private GeneralNames generateGeneralNames(X500Name principal) + { + return new GeneralNames(new GeneralName(principal)); + } + + private boolean matchesDN(X500Name subject, GeneralNames targets) + { + GeneralName[] names = targets.getNames(); + + for (int i = 0; i != names.length; i++) + { + GeneralName gn = names[i]; + + if (gn.getTagNo() == GeneralName.directoryName) + { + if (X500Name.getInstance(gn.getName()).equals(subject)) + { + return true; + } + } + } + + return false; + } + + private X500Name[] getPrincipals(GeneralName[] names) + { + List l = new ArrayList(names.length); + + for (int i = 0; i != names.length; i++) + { + if (names[i].getTagNo() == GeneralName.directoryName) + { + l.add(X500Name.getInstance(names[i].getName())); + } + } + + return (X500Name[])l.toArray(new X500Name[l.size()]); + } + + /** + * Return any principal objects inside the attribute certificate holder + * entity names field. + * + * @return an array of Principal objects (usually X500Principal), null if no + * entity names field is set. + */ + public X500Name[] getEntityNames() + { + if (holder.getEntityName() != null) + { + return getPrincipals(holder.getEntityName().getNames()); + } + + return null; + } + + /** + * Return the principals associated with the issuer attached to this holder + * + * @return an array of principals, null if no BaseCertificateID is set. + */ + public X500Name[] getIssuer() + { + if (holder.getBaseCertificateID() != null) + { + return getPrincipals(holder.getBaseCertificateID().getIssuer().getNames()); + } + + return null; + } + + /** + * Return the serial number associated with the issuer attached to this + * holder. + * + * @return the certificate serial number, null if no BaseCertificateID is + * set. + */ + public BigInteger getSerialNumber() + { + if (holder.getBaseCertificateID() != null) + { + return holder.getBaseCertificateID().getSerial().getValue(); + } + + return null; + } + + public Object clone() + { + return new AttributeCertificateHolder((ASN1Sequence)holder.toASN1Primitive()); + } + + public boolean match(Object obj) + { + if (!(obj instanceof X509CertificateHolder)) + { + return false; + } + + X509CertificateHolder x509Cert = (X509CertificateHolder)obj; + + if (holder.getBaseCertificateID() != null) + { + return holder.getBaseCertificateID().getSerial().getValue().equals(x509Cert.getSerialNumber()) + && matchesDN(x509Cert.getIssuer(), holder.getBaseCertificateID().getIssuer()); + } + + if (holder.getEntityName() != null) + { + if (matchesDN(x509Cert.getSubject(), + holder.getEntityName())) + { + return true; + } + } + + if (holder.getObjectDigestInfo() != null) + { + try + { + DigestCalculator digCalc = digestCalculatorProvider.get(holder.getObjectDigestInfo().getDigestAlgorithm()); + OutputStream digOut = digCalc.getOutputStream(); + + switch (getDigestedObjectType()) + { + case ObjectDigestInfo.publicKey: + // TODO: DSA Dss-parms + digOut.write(x509Cert.getSubjectPublicKeyInfo().getEncoded()); + break; + case ObjectDigestInfo.publicKeyCert: + digOut.write(x509Cert.getEncoded()); + break; + } + + digOut.close(); + + if (!Arrays.areEqual(digCalc.getDigest(), getObjectDigest())) + { + return false; + } + } + catch (Exception e) + { + return false; + } + } + + return false; + } + + public boolean equals(Object obj) + { + if (obj == this) + { + return true; + } + + if (!(obj instanceof AttributeCertificateHolder)) + { + return false; + } + + AttributeCertificateHolder other = (AttributeCertificateHolder)obj; + + return this.holder.equals(other.holder); + } + + public int hashCode() + { + return this.holder.hashCode(); + } + + /** + * Set a digest calculator provider to be used if matches are attempted using + * ObjectDigestInfo, + * + * @param digCalcProvider a provider of digest calculators. + */ + public static void setDigestCalculatorProvider(DigestCalculatorProvider digCalcProvider) + { + digestCalculatorProvider = digCalcProvider; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/AttributeCertificateIssuer.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/AttributeCertificateIssuer.java new file mode 100644 index 000000000..e659e5924 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/AttributeCertificateIssuer.java @@ -0,0 +1,147 @@ +package org.spongycastle.cert; + +import java.util.ArrayList; +import java.util.List; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x509.AttCertIssuer; +import org.spongycastle.asn1.x509.GeneralName; +import org.spongycastle.asn1.x509.GeneralNames; +import org.spongycastle.asn1.x509.V2Form; +import org.spongycastle.util.Selector; + +/** + * Carrying class for an attribute certificate issuer. + */ +public class AttributeCertificateIssuer + implements Selector +{ + final ASN1Encodable form; + + /** + * Set the issuer directly with the ASN.1 structure. + * + * @param issuer The issuer + */ + public AttributeCertificateIssuer(AttCertIssuer issuer) + { + form = issuer.getIssuer(); + } + + public AttributeCertificateIssuer(X500Name principal) + { + form = new V2Form(new GeneralNames(new GeneralName(principal))); + } + + public X500Name[] getNames() + { + GeneralNames name; + + if (form instanceof V2Form) + { + name = ((V2Form)form).getIssuerName(); + } + else + { + name = (GeneralNames)form; + } + + GeneralName[] names = name.getNames(); + + List l = new ArrayList(names.length); + + for (int i = 0; i != names.length; i++) + { + if (names[i].getTagNo() == GeneralName.directoryName) + { + l.add(X500Name.getInstance(names[i].getName())); + } + } + + return (X500Name[])l.toArray(new X500Name[l.size()]); + } + + private boolean matchesDN(X500Name subject, GeneralNames targets) + { + GeneralName[] names = targets.getNames(); + + for (int i = 0; i != names.length; i++) + { + GeneralName gn = names[i]; + + if (gn.getTagNo() == GeneralName.directoryName) + { + if (X500Name.getInstance(gn.getName()).equals(subject)) + { + return true; + } + } + } + + return false; + } + + public Object clone() + { + return new AttributeCertificateIssuer(AttCertIssuer.getInstance(form)); + } + + public boolean equals(Object obj) + { + if (obj == this) + { + return true; + } + + if (!(obj instanceof AttributeCertificateIssuer)) + { + return false; + } + + AttributeCertificateIssuer other = (AttributeCertificateIssuer)obj; + + return this.form.equals(other.form); + } + + public int hashCode() + { + return this.form.hashCode(); + } + + public boolean match(Object obj) + { + if (!(obj instanceof X509CertificateHolder)) + { + return false; + } + + X509CertificateHolder x509Cert = (X509CertificateHolder)obj; + + if (form instanceof V2Form) + { + V2Form issuer = (V2Form)form; + if (issuer.getBaseCertificateID() != null) + { + return issuer.getBaseCertificateID().getSerial().getValue().equals(x509Cert.getSerialNumber()) + && matchesDN(x509Cert.getIssuer(), issuer.getBaseCertificateID().getIssuer()); + } + + GeneralNames name = issuer.getIssuerName(); + if (matchesDN(x509Cert.getSubject(), name)) + { + return true; + } + } + else + { + GeneralNames name = (GeneralNames)form; + if (matchesDN(x509Cert.getSubject(), name)) + { + return true; + } + } + + return false; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/CertException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/CertException.java new file mode 100644 index 000000000..b394ec895 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/CertException.java @@ -0,0 +1,27 @@ +package org.spongycastle.cert; + +/** + * General checked Exception thrown in the cert package and its sub-packages. + */ +public class CertException + extends Exception +{ + private Throwable cause; + + public CertException(String msg, Throwable cause) + { + super(msg); + + this.cause = cause; + } + + public CertException(String msg) + { + super(msg); + } + + public Throwable getCause() + { + return cause; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/CertIOException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/CertIOException.java new file mode 100644 index 000000000..f21641d62 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/CertIOException.java @@ -0,0 +1,29 @@ +package org.spongycastle.cert; + +import java.io.IOException; + +/** + * General IOException thrown in the cert package and its sub-packages. + */ +public class CertIOException + extends IOException +{ + private Throwable cause; + + public CertIOException(String msg, Throwable cause) + { + super(msg); + + this.cause = cause; + } + + public CertIOException(String msg) + { + super(msg); + } + + public Throwable getCause() + { + return cause; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/CertRuntimeException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/CertRuntimeException.java new file mode 100644 index 000000000..a797083e6 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/CertRuntimeException.java @@ -0,0 +1,19 @@ +package org.spongycastle.cert; + +public class CertRuntimeException + extends RuntimeException +{ + private Throwable cause; + + public CertRuntimeException(String msg, Throwable cause) + { + super(msg); + + this.cause = cause; + } + + public Throwable getCause() + { + return cause; + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/CertUtils.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/CertUtils.java new file mode 100644 index 000000000..d03f78437 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/CertUtils.java @@ -0,0 +1,244 @@ +package org.spongycastle.cert; + +import java.io.IOException; +import java.io.OutputStream; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1GeneralizedTime; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.DERBitString; +import org.spongycastle.asn1.DERNull; +import org.spongycastle.asn1.DEROutputStream; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.AttributeCertificate; +import org.spongycastle.asn1.x509.AttributeCertificateInfo; +import org.spongycastle.asn1.x509.Certificate; +import org.spongycastle.asn1.x509.CertificateList; +import org.spongycastle.asn1.x509.Extensions; +import org.spongycastle.asn1.x509.ExtensionsGenerator; +import org.spongycastle.asn1.x509.TBSCertList; +import org.spongycastle.asn1.x509.TBSCertificate; +import org.spongycastle.operator.ContentSigner; + +class CertUtils +{ + private static Set EMPTY_SET = Collections.unmodifiableSet(new HashSet()); + private static List EMPTY_LIST = Collections.unmodifiableList(new ArrayList()); + + static X509CertificateHolder generateFullCert(ContentSigner signer, TBSCertificate tbsCert) + { + try + { + return new X509CertificateHolder(generateStructure(tbsCert, signer.getAlgorithmIdentifier(), generateSig(signer, tbsCert))); + } + catch (IOException e) + { + throw new IllegalStateException("cannot produce certificate signature"); + } + } + + static X509AttributeCertificateHolder generateFullAttrCert(ContentSigner signer, AttributeCertificateInfo attrInfo) + { + try + { + return new X509AttributeCertificateHolder(generateAttrStructure(attrInfo, signer.getAlgorithmIdentifier(), generateSig(signer, attrInfo))); + } + catch (IOException e) + { + throw new IllegalStateException("cannot produce attribute certificate signature"); + } + } + + static X509CRLHolder generateFullCRL(ContentSigner signer, TBSCertList tbsCertList) + { + try + { + return new X509CRLHolder(generateCRLStructure(tbsCertList, signer.getAlgorithmIdentifier(), generateSig(signer, tbsCertList))); + } + catch (IOException e) + { + throw new IllegalStateException("cannot produce certificate signature"); + } + } + + private static byte[] generateSig(ContentSigner signer, ASN1Encodable tbsObj) + throws IOException + { + OutputStream sOut = signer.getOutputStream(); + DEROutputStream dOut = new DEROutputStream(sOut); + + dOut.writeObject(tbsObj); + + sOut.close(); + + return signer.getSignature(); + } + + private static Certificate generateStructure(TBSCertificate tbsCert, AlgorithmIdentifier sigAlgId, byte[] signature) + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(tbsCert); + v.add(sigAlgId); + v.add(new DERBitString(signature)); + + return Certificate.getInstance(new DERSequence(v)); + } + + private static AttributeCertificate generateAttrStructure(AttributeCertificateInfo attrInfo, AlgorithmIdentifier sigAlgId, byte[] signature) + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(attrInfo); + v.add(sigAlgId); + v.add(new DERBitString(signature)); + + return AttributeCertificate.getInstance(new DERSequence(v)); + } + + private static CertificateList generateCRLStructure(TBSCertList tbsCertList, AlgorithmIdentifier sigAlgId, byte[] signature) + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(tbsCertList); + v.add(sigAlgId); + v.add(new DERBitString(signature)); + + return CertificateList.getInstance(new DERSequence(v)); + } + + static Set getCriticalExtensionOIDs(Extensions extensions) + { + if (extensions == null) + { + return EMPTY_SET; + } + + return Collections.unmodifiableSet(new HashSet(Arrays.asList(extensions.getCriticalExtensionOIDs()))); + } + + static Set getNonCriticalExtensionOIDs(Extensions extensions) + { + if (extensions == null) + { + return EMPTY_SET; + } + + // TODO: should probably produce a set that imposes correct ordering + return Collections.unmodifiableSet(new HashSet(Arrays.asList(extensions.getNonCriticalExtensionOIDs()))); + } + + static List getExtensionOIDs(Extensions extensions) + { + if (extensions == null) + { + return EMPTY_LIST; + } + + return Collections.unmodifiableList(Arrays.asList(extensions.getExtensionOIDs())); + } + + static void addExtension(ExtensionsGenerator extGenerator, ASN1ObjectIdentifier oid, boolean isCritical, ASN1Encodable value) + throws CertIOException + { + try + { + extGenerator.addExtension(oid, isCritical, value); + } + catch (IOException e) + { + throw new CertIOException("cannot encode extension: " + e.getMessage(), e); + } + } + + static DERBitString booleanToBitString(boolean[] id) + { + byte[] bytes = new byte[(id.length + 7) / 8]; + + for (int i = 0; i != id.length; i++) + { + bytes[i / 8] |= (id[i]) ? (1 << ((7 - (i % 8)))) : 0; + } + + int pad = id.length % 8; + + if (pad == 0) + { + return new DERBitString(bytes); + } + else + { + return new DERBitString(bytes, 8 - pad); + } + } + + static boolean[] bitStringToBoolean(DERBitString bitString) + { + if (bitString != null) + { + byte[] bytes = bitString.getBytes(); + boolean[] boolId = new boolean[bytes.length * 8 - bitString.getPadBits()]; + + for (int i = 0; i != boolId.length; i++) + { + boolId[i] = (bytes[i / 8] & (0x80 >>> (i % 8))) != 0; + } + + return boolId; + } + + return null; + } + + static Date recoverDate(ASN1GeneralizedTime time) + { + try + { + return time.getDate(); + } + catch (ParseException e) + { + throw new IllegalStateException("unable to recover date: " + e.getMessage()); + } + } + + static boolean isAlgIdEqual(AlgorithmIdentifier id1, AlgorithmIdentifier id2) + { + if (!id1.getAlgorithm().equals(id2.getAlgorithm())) + { + return false; + } + + if (id1.getParameters() == null) + { + if (id2.getParameters() != null && !id2.getParameters().equals(DERNull.INSTANCE)) + { + return false; + } + + return true; + } + + if (id2.getParameters() == null) + { + if (id1.getParameters() != null && !id1.getParameters().equals(DERNull.INSTANCE)) + { + return false; + } + + return true; + } + + return id1.getParameters().equals(id2.getParameters()); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/X509AttributeCertificateHolder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/X509AttributeCertificateHolder.java new file mode 100644 index 000000000..76c0cdf95 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/X509AttributeCertificateHolder.java @@ -0,0 +1,366 @@ +package org.spongycastle.cert; + +import java.io.IOException; +import java.io.OutputStream; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Set; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.DEROutputStream; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.AttCertValidityPeriod; +import org.spongycastle.asn1.x509.Attribute; +import org.spongycastle.asn1.x509.AttributeCertificate; +import org.spongycastle.asn1.x509.AttributeCertificateInfo; +import org.spongycastle.asn1.x509.Extension; +import org.spongycastle.asn1.x509.Extensions; +import org.spongycastle.operator.ContentVerifier; +import org.spongycastle.operator.ContentVerifierProvider; + +/** + * Holding class for an X.509 AttributeCertificate structure. + */ +public class X509AttributeCertificateHolder +{ + private static Attribute[] EMPTY_ARRAY = new Attribute[0]; + + private AttributeCertificate attrCert; + private Extensions extensions; + + private static AttributeCertificate parseBytes(byte[] certEncoding) + throws IOException + { + try + { + return AttributeCertificate.getInstance(ASN1Primitive.fromByteArray(certEncoding)); + } + catch (ClassCastException e) + { + throw new CertIOException("malformed data: " + e.getMessage(), e); + } + catch (IllegalArgumentException e) + { + throw new CertIOException("malformed data: " + e.getMessage(), e); + } + } + + /** + * Create a X509AttributeCertificateHolder from the passed in bytes. + * + * @param certEncoding BER/DER encoding of the certificate. + * @throws IOException in the event of corrupted data, or an incorrect structure. + */ + public X509AttributeCertificateHolder(byte[] certEncoding) + throws IOException + { + this(parseBytes(certEncoding)); + } + + /** + * Create a X509AttributeCertificateHolder from the passed in ASN.1 structure. + * + * @param attrCert an ASN.1 AttributeCertificate structure. + */ + public X509AttributeCertificateHolder(AttributeCertificate attrCert) + { + this.attrCert = attrCert; + this.extensions = attrCert.getAcinfo().getExtensions(); + } + + /** + * Return the ASN.1 encoding of this holder's attribute certificate. + * + * @return a DER encoded byte array. + * @throws IOException if an encoding cannot be generated. + */ + public byte[] getEncoded() + throws IOException + { + return attrCert.getEncoded(); + } + + public int getVersion() + { + return attrCert.getAcinfo().getVersion().getValue().intValue() + 1; + } + + /** + * Return the serial number of this attribute certificate. + * + * @return the serial number. + */ + public BigInteger getSerialNumber() + { + return attrCert.getAcinfo().getSerialNumber().getValue(); + } + + /** + * Return the holder details for this attribute certificate. + * + * @return this attribute certificate's holder structure. + */ + public AttributeCertificateHolder getHolder() + { + return new AttributeCertificateHolder((ASN1Sequence)attrCert.getAcinfo().getHolder().toASN1Primitive()); + } + + /** + * Return the issuer details for this attribute certificate. + * + * @return this attribute certificate's issuer structure, + */ + public AttributeCertificateIssuer getIssuer() + { + return new AttributeCertificateIssuer(attrCert.getAcinfo().getIssuer()); + } + + /** + * Return the date before which this attribute certificate is not valid. + * + * @return the start date for the attribute certificate's validity period. + */ + public Date getNotBefore() + { + return CertUtils.recoverDate(attrCert.getAcinfo().getAttrCertValidityPeriod().getNotBeforeTime()); + } + + /** + * Return the date after which this attribute certificate is not valid. + * + * @return the final date for the attribute certificate's validity period. + */ + public Date getNotAfter() + { + return CertUtils.recoverDate(attrCert.getAcinfo().getAttrCertValidityPeriod().getNotAfterTime()); + } + + /** + * Return the attributes, if any associated with this request. + * + * @return an array of Attribute, zero length if none present. + */ + public Attribute[] getAttributes() + { + ASN1Sequence seq = attrCert.getAcinfo().getAttributes(); + Attribute[] attrs = new Attribute[seq.size()]; + + for (int i = 0; i != seq.size(); i++) + { + attrs[i] = Attribute.getInstance(seq.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) + { + ASN1Sequence seq = attrCert.getAcinfo().getAttributes(); + List list = new ArrayList(); + + for (int i = 0; i != seq.size(); i++) + { + Attribute attr = Attribute.getInstance(seq.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()]); + } + + /** + * Return whether or not the holder's attribute certificate contains extensions. + * + * @return true if extension are present, false otherwise. + */ + public boolean hasExtensions() + { + return extensions != null; + } + + /** + * Look up the extension associated with the passed in OID. + * + * @param oid the OID of the extension of interest. + * + * @return the extension if present, null otherwise. + */ + public Extension getExtension(ASN1ObjectIdentifier oid) + { + if (extensions != null) + { + return extensions.getExtension(oid); + } + + return null; + } + + /** + * Return the extensions block associated with this certificate if there is one. + * + * @return the extensions block, null otherwise. + */ + public Extensions getExtensions() + { + return extensions; + } + + /** + * Returns a list of ASN1ObjectIdentifier objects representing the OIDs of the + * extensions contained in this holder's attribute certificate. + * + * @return a list of extension OIDs. + */ + public List getExtensionOIDs() + { + return CertUtils.getExtensionOIDs(extensions); + } + + /** + * Returns a set of ASN1ObjectIdentifier objects representing the OIDs of the + * critical extensions contained in this holder's attribute certificate. + * + * @return a set of critical extension OIDs. + */ + public Set getCriticalExtensionOIDs() + { + return CertUtils.getCriticalExtensionOIDs(extensions); + } + + /** + * Returns a set of ASN1ObjectIdentifier objects representing the OIDs of the + * non-critical extensions contained in this holder's attribute certificate. + * + * @return a set of non-critical extension OIDs. + */ + public Set getNonCriticalExtensionOIDs() + { + return CertUtils.getNonCriticalExtensionOIDs(extensions); + } + + public boolean[] getIssuerUniqueID() + { + return CertUtils.bitStringToBoolean(attrCert.getAcinfo().getIssuerUniqueID()); + } + + /** + * Return the details of the signature algorithm used to create this attribute certificate. + * + * @return the AlgorithmIdentifier describing the signature algorithm used to create this attribute certificate. + */ + public AlgorithmIdentifier getSignatureAlgorithm() + { + return attrCert.getSignatureAlgorithm(); + } + + /** + * Return the bytes making up the signature associated with this attribute certificate. + * + * @return the attribute certificate signature bytes. + */ + public byte[] getSignature() + { + return attrCert.getSignatureValue().getBytes(); + } + + /** + * Return the underlying ASN.1 structure for the attribute certificate in this holder. + * + * @return a AttributeCertificate object. + */ + public AttributeCertificate toASN1Structure() + { + return attrCert; + } + + /** + * Return whether or not this attribute certificate is valid on a particular date. + * + * @param date the date of interest. + * @return true if the attribute certificate is valid, false otherwise. + */ + public boolean isValidOn(Date date) + { + AttCertValidityPeriod certValidityPeriod = attrCert.getAcinfo().getAttrCertValidityPeriod(); + + return !date.before(CertUtils.recoverDate(certValidityPeriod.getNotBeforeTime())) && !date.after(CertUtils.recoverDate(certValidityPeriod.getNotAfterTime())); + } + + /** + * Validate the signature on the attribute certificate 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 CertException if the signature cannot be processed or is inappropriate. + */ + public boolean isSignatureValid(ContentVerifierProvider verifierProvider) + throws CertException + { + AttributeCertificateInfo acinfo = attrCert.getAcinfo(); + + if (!CertUtils.isAlgIdEqual(acinfo.getSignature(), attrCert.getSignatureAlgorithm())) + { + throw new CertException("signature invalid - algorithm identifier mismatch"); + } + + ContentVerifier verifier; + + try + { + verifier = verifierProvider.get((acinfo.getSignature())); + + OutputStream sOut = verifier.getOutputStream(); + DEROutputStream dOut = new DEROutputStream(sOut); + + dOut.writeObject(acinfo); + + sOut.close(); + } + catch (Exception e) + { + throw new CertException("unable to process signature: " + e.getMessage(), e); + } + + return verifier.verify(attrCert.getSignatureValue().getBytes()); + } + + public boolean equals( + Object o) + { + if (o == this) + { + return true; + } + + if (!(o instanceof X509AttributeCertificateHolder)) + { + return false; + } + + X509AttributeCertificateHolder other = (X509AttributeCertificateHolder)o; + + return this.attrCert.equals(other.attrCert); + } + + public int hashCode() + { + return this.attrCert.hashCode(); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/X509CRLEntryHolder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/X509CRLEntryHolder.java new file mode 100644 index 000000000..da542c0e6 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/X509CRLEntryHolder.java @@ -0,0 +1,144 @@ +package org.spongycastle.cert; + +import java.math.BigInteger; +import java.util.Date; +import java.util.List; +import java.util.Set; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.x509.Extension; +import org.spongycastle.asn1.x509.Extensions; +import org.spongycastle.asn1.x509.GeneralNames; +import org.spongycastle.asn1.x509.TBSCertList; + +/** + * Holding class for an X.509 CRL Entry structure. + */ +public class X509CRLEntryHolder +{ + private TBSCertList.CRLEntry entry; + private GeneralNames ca; + + X509CRLEntryHolder(TBSCertList.CRLEntry entry, boolean isIndirect, GeneralNames previousCA) + { + this.entry = entry; + this.ca = previousCA; + + if (isIndirect && entry.hasExtensions()) + { + Extension currentCaName = entry.getExtensions().getExtension(Extension.certificateIssuer); + + if (currentCaName != null) + { + ca = GeneralNames.getInstance(currentCaName.getParsedValue()); + } + } + } + + /** + * Return the serial number of the certificate associated with this CRLEntry. + * + * @return the revoked certificate's serial number. + */ + public BigInteger getSerialNumber() + { + return entry.getUserCertificate().getValue(); + } + + /** + * Return the date on which the certificate associated with this CRLEntry was revoked. + * + * @return the revocation date for the revoked certificate. + */ + public Date getRevocationDate() + { + return entry.getRevocationDate().getDate(); + } + + /** + * Return whether or not the holder's CRL entry contains extensions. + * + * @return true if extension are present, false otherwise. + */ + public boolean hasExtensions() + { + return entry.hasExtensions(); + } + + /** + * Return the available names for the certificate issuer for the certificate referred to by this CRL entry. + * <p> + * Note: this will be the issuer of the CRL unless it has been specified that the CRL is indirect + * in the IssuingDistributionPoint extension and either a previous entry, or the current one, + * has specified a different CA via the certificateIssuer extension. + * </p> + * + * @return the revoked certificate's issuer. + */ + public GeneralNames getCertificateIssuer() + { + return this.ca; + } + + /** + * Look up the extension associated with the passed in OID. + * + * @param oid the OID of the extension of interest. + * + * @return the extension if present, null otherwise. + */ + public Extension getExtension(ASN1ObjectIdentifier oid) + { + Extensions extensions = entry.getExtensions(); + + if (extensions != null) + { + return extensions.getExtension(oid); + } + + return null; + } + + /** + * Return the extensions block associated with this CRL entry if there is one. + * + * @return the extensions block, null otherwise. + */ + public Extensions getExtensions() + { + return entry.getExtensions(); + } + + /** + * Returns a list of ASN1ObjectIdentifier objects representing the OIDs of the + * extensions contained in this holder's CRL entry. + * + * @return a list of extension OIDs. + */ + public List getExtensionOIDs() + { + return CertUtils.getExtensionOIDs(entry.getExtensions()); + } + + /** + * Returns a set of ASN1ObjectIdentifier objects representing the OIDs of the + * critical extensions contained in this holder's CRL entry. + * + * @return a set of critical extension OIDs. + */ + public Set getCriticalExtensionOIDs() + { + return CertUtils.getCriticalExtensionOIDs(entry.getExtensions()); + } + + /** + * Returns a set of ASN1ObjectIdentifier objects representing the OIDs of the + * non-critical extensions contained in this holder's CRL entry. + * + * @return a set of non-critical extension OIDs. + */ + public Set getNonCriticalExtensionOIDs() + { + return CertUtils.getNonCriticalExtensionOIDs(entry.getExtensions()); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/X509CRLHolder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/X509CRLHolder.java new file mode 100644 index 000000000..e94c2c1b4 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/X509CRLHolder.java @@ -0,0 +1,317 @@ +package org.spongycastle.cert; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.List; +import java.util.Set; + +import org.spongycastle.asn1.ASN1InputStream; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.DEROutputStream; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x509.CertificateList; +import org.spongycastle.asn1.x509.Extension; +import org.spongycastle.asn1.x509.Extensions; +import org.spongycastle.asn1.x509.GeneralName; +import org.spongycastle.asn1.x509.GeneralNames; +import org.spongycastle.asn1.x509.IssuingDistributionPoint; +import org.spongycastle.asn1.x509.TBSCertList; +import org.spongycastle.operator.ContentVerifier; +import org.spongycastle.operator.ContentVerifierProvider; + +/** + * Holding class for an X.509 CRL structure. + */ +public class X509CRLHolder +{ + private CertificateList x509CRL; + private boolean isIndirect; + private Extensions extensions; + private GeneralNames issuerName; + + private static CertificateList parseStream(InputStream stream) + throws IOException + { + try + { + return CertificateList.getInstance(new ASN1InputStream(stream, true).readObject()); + } + catch (ClassCastException e) + { + throw new CertIOException("malformed data: " + e.getMessage(), e); + } + catch (IllegalArgumentException e) + { + throw new CertIOException("malformed data: " + e.getMessage(), e); + } + } + + private static boolean isIndirectCRL(Extensions extensions) + { + if (extensions == null) + { + return false; + } + + Extension ext = extensions.getExtension(Extension.issuingDistributionPoint); + + return ext != null && IssuingDistributionPoint.getInstance(ext.getParsedValue()).isIndirectCRL(); + } + + /** + * Create a X509CRLHolder from the passed in bytes. + * + * @param crlEncoding BER/DER encoding of the CRL + * @throws IOException in the event of corrupted data, or an incorrect structure. + */ + public X509CRLHolder(byte[] crlEncoding) + throws IOException + { + this(parseStream(new ByteArrayInputStream(crlEncoding))); + } + + /** + * Create a X509CRLHolder from the passed in InputStream. + * + * @param crlStream BER/DER encoded InputStream of the CRL + * @throws IOException in the event of corrupted data, or an incorrect structure. + */ + public X509CRLHolder(InputStream crlStream) + throws IOException + { + this(parseStream(crlStream)); + } + + /** + * Create a X509CRLHolder from the passed in ASN.1 structure. + * + * @param x509CRL an ASN.1 CertificateList structure. + */ + public X509CRLHolder(CertificateList x509CRL) + { + this.x509CRL = x509CRL; + this.extensions = x509CRL.getTBSCertList().getExtensions(); + this.isIndirect = isIndirectCRL(extensions); + this.issuerName = new GeneralNames(new GeneralName(x509CRL.getIssuer())); + } + + /** + * Return the ASN.1 encoding of this holder's CRL. + * + * @return a DER encoded byte array. + * @throws IOException if an encoding cannot be generated. + */ + public byte[] getEncoded() + throws IOException + { + return x509CRL.getEncoded(); + } + + /** + * Return the issuer of this holder's CRL. + * + * @return the CRL issuer. + */ + public X500Name getIssuer() + { + return X500Name.getInstance(x509CRL.getIssuer()); + } + + public X509CRLEntryHolder getRevokedCertificate(BigInteger serialNumber) + { + GeneralNames currentCA = issuerName; + for (Enumeration en = x509CRL.getRevokedCertificateEnumeration(); en.hasMoreElements();) + { + TBSCertList.CRLEntry entry = (TBSCertList.CRLEntry)en.nextElement(); + + if (entry.getUserCertificate().getValue().equals(serialNumber)) + { + return new X509CRLEntryHolder(entry, isIndirect, currentCA); + } + + if (isIndirect && entry.hasExtensions()) + { + Extension currentCaName = entry.getExtensions().getExtension(Extension.certificateIssuer); + + if (currentCaName != null) + { + currentCA = GeneralNames.getInstance(currentCaName.getParsedValue()); + } + } + } + + return null; + } + + /** + * Return a collection of X509CRLEntryHolder objects, giving the details of the + * revoked certificates that appear on this CRL. + * + * @return the revoked certificates as a collection of X509CRLEntryHolder objects. + */ + public Collection getRevokedCertificates() + { + TBSCertList.CRLEntry[] entries = x509CRL.getRevokedCertificates(); + List l = new ArrayList(entries.length); + GeneralNames currentCA = issuerName; + + for (Enumeration en = x509CRL.getRevokedCertificateEnumeration(); en.hasMoreElements();) + { + TBSCertList.CRLEntry entry = (TBSCertList.CRLEntry)en.nextElement(); + X509CRLEntryHolder crlEntry = new X509CRLEntryHolder(entry, isIndirect, currentCA); + + l.add(crlEntry); + + currentCA = crlEntry.getCertificateIssuer(); + } + + return l; + } + + /** + * Return whether or not the holder's CRL contains extensions. + * + * @return true if extension are present, false otherwise. + */ + public boolean hasExtensions() + { + return extensions != null; + } + + /** + * Look up the extension associated with the passed in OID. + * + * @param oid the OID of the extension of interest. + * + * @return the extension if present, null otherwise. + */ + public Extension getExtension(ASN1ObjectIdentifier oid) + { + if (extensions != null) + { + return extensions.getExtension(oid); + } + + return null; + } + + /** + * Return the extensions block associated with this CRL if there is one. + * + * @return the extensions block, null otherwise. + */ + public Extensions getExtensions() + { + return extensions; + } + + /** + * Returns a list of ASN1ObjectIdentifier objects representing the OIDs of the + * extensions contained in this holder's CRL. + * + * @return a list of extension OIDs. + */ + public List getExtensionOIDs() + { + return CertUtils.getExtensionOIDs(extensions); + } + + /** + * Returns a set of ASN1ObjectIdentifier objects representing the OIDs of the + * critical extensions contained in this holder's CRL. + * + * @return a set of critical extension OIDs. + */ + public Set getCriticalExtensionOIDs() + { + return CertUtils.getCriticalExtensionOIDs(extensions); + } + + /** + * Returns a set of ASN1ObjectIdentifier objects representing the OIDs of the + * non-critical extensions contained in this holder's CRL. + * + * @return a set of non-critical extension OIDs. + */ + public Set getNonCriticalExtensionOIDs() + { + return CertUtils.getNonCriticalExtensionOIDs(extensions); + } + + /** + * Return the underlying ASN.1 structure for the CRL in this holder. + * + * @return a CertificateList object. + */ + public CertificateList toASN1Structure() + { + return x509CRL; + } + + /** + * Validate the signature on the CRL. + * + * @param verifierProvider a ContentVerifierProvider that can generate a verifier for the signature. + * @return true if the signature is valid, false otherwise. + * @throws CertException if the signature cannot be processed or is inappropriate. + */ + public boolean isSignatureValid(ContentVerifierProvider verifierProvider) + throws CertException + { + TBSCertList tbsCRL = x509CRL.getTBSCertList(); + + if (!CertUtils.isAlgIdEqual(tbsCRL.getSignature(), x509CRL.getSignatureAlgorithm())) + { + throw new CertException("signature invalid - algorithm identifier mismatch"); + } + + ContentVerifier verifier; + + try + { + verifier = verifierProvider.get((tbsCRL.getSignature())); + + OutputStream sOut = verifier.getOutputStream(); + DEROutputStream dOut = new DEROutputStream(sOut); + + dOut.writeObject(tbsCRL); + + sOut.close(); + } + catch (Exception e) + { + throw new CertException("unable to process signature: " + e.getMessage(), e); + } + + return verifier.verify(x509CRL.getSignature().getBytes()); + } + + public boolean equals( + Object o) + { + if (o == this) + { + return true; + } + + if (!(o instanceof X509CRLHolder)) + { + return false; + } + + X509CRLHolder other = (X509CRLHolder)o; + + return this.x509CRL.equals(other.x509CRL); + } + + public int hashCode() + { + return this.x509CRL.hashCode(); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/X509CertificateHolder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/X509CertificateHolder.java new file mode 100644 index 000000000..fd53b92ca --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/X509CertificateHolder.java @@ -0,0 +1,327 @@ +package org.spongycastle.cert; + +import java.io.IOException; +import java.io.OutputStream; +import java.math.BigInteger; +import java.util.Date; +import java.util.List; +import java.util.Set; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.DEROutputStream; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.Certificate; +import org.spongycastle.asn1.x509.Extension; +import org.spongycastle.asn1.x509.Extensions; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.asn1.x509.TBSCertificate; +import org.spongycastle.operator.ContentVerifier; +import org.spongycastle.operator.ContentVerifierProvider; + +/** + * Holding class for an X.509 Certificate structure. + */ +public class X509CertificateHolder +{ + private Certificate x509Certificate; + private Extensions extensions; + + private static Certificate parseBytes(byte[] certEncoding) + throws IOException + { + try + { + return Certificate.getInstance(ASN1Primitive.fromByteArray(certEncoding)); + } + catch (ClassCastException e) + { + throw new CertIOException("malformed data: " + e.getMessage(), e); + } + catch (IllegalArgumentException e) + { + throw new CertIOException("malformed data: " + e.getMessage(), e); + } + } + + /** + * Create a X509CertificateHolder from the passed in bytes. + * + * @param certEncoding BER/DER encoding of the certificate. + * @throws IOException in the event of corrupted data, or an incorrect structure. + */ + public X509CertificateHolder(byte[] certEncoding) + throws IOException + { + this(parseBytes(certEncoding)); + } + + /** + * Create a X509CertificateHolder from the passed in ASN.1 structure. + * + * @param x509Certificate an ASN.1 Certificate structure. + */ + public X509CertificateHolder(Certificate x509Certificate) + { + this.x509Certificate = x509Certificate; + this.extensions = x509Certificate.getTBSCertificate().getExtensions(); + } + + public int getVersionNumber() + { + return x509Certificate.getVersionNumber(); + } + + /** + * @deprecated use getVersionNumber + */ + public int getVersion() + { + return x509Certificate.getVersionNumber(); + } + + /** + * Return whether or not the holder's certificate contains extensions. + * + * @return true if extension are present, false otherwise. + */ + public boolean hasExtensions() + { + return extensions != null; + } + + /** + * Look up the extension associated with the passed in OID. + * + * @param oid the OID of the extension of interest. + * + * @return the extension if present, null otherwise. + */ + public Extension getExtension(ASN1ObjectIdentifier oid) + { + if (extensions != null) + { + return extensions.getExtension(oid); + } + + return null; + } + + /** + * Return the extensions block associated with this certificate if there is one. + * + * @return the extensions block, null otherwise. + */ + public Extensions getExtensions() + { + return extensions; + } + + /** + * Returns a list of ASN1ObjectIdentifier objects representing the OIDs of the + * extensions contained in this holder's certificate. + * + * @return a list of extension OIDs. + */ + public List getExtensionOIDs() + { + return CertUtils.getExtensionOIDs(extensions); + } + + /** + * Returns a set of ASN1ObjectIdentifier objects representing the OIDs of the + * critical extensions contained in this holder's certificate. + * + * @return a set of critical extension OIDs. + */ + public Set getCriticalExtensionOIDs() + { + return CertUtils.getCriticalExtensionOIDs(extensions); + } + + /** + * Returns a set of ASN1ObjectIdentifier objects representing the OIDs of the + * non-critical extensions contained in this holder's certificate. + * + * @return a set of non-critical extension OIDs. + */ + public Set getNonCriticalExtensionOIDs() + { + return CertUtils.getNonCriticalExtensionOIDs(extensions); + } + + /** + * Return the serial number of this attribute certificate. + * + * @return the serial number. + */ + public BigInteger getSerialNumber() + { + return x509Certificate.getSerialNumber().getValue(); + } + + /** + * Return the issuer of this certificate. + * + * @return the certificate issuer. + */ + public X500Name getIssuer() + { + return X500Name.getInstance(x509Certificate.getIssuer()); + } + + /** + * Return the subject this certificate is for. + * + * @return the subject for the certificate. + */ + public X500Name getSubject() + { + return X500Name.getInstance(x509Certificate.getSubject()); + } + + /** + * Return the date before which this certificate is not valid. + * + * @return the start time for the certificate's validity period. + */ + public Date getNotBefore() + { + return x509Certificate.getStartDate().getDate(); + } + + /** + * Return the date after which this certificate is not valid. + * + * @return the final time for the certificate's validity period. + */ + public Date getNotAfter() + { + return x509Certificate.getEndDate().getDate(); + } + + /** + * Return the SubjectPublicKeyInfo describing the public key this certificate is carrying. + * + * @return the public key ASN.1 structure contained in the certificate. + */ + public SubjectPublicKeyInfo getSubjectPublicKeyInfo() + { + return x509Certificate.getSubjectPublicKeyInfo(); + } + + /** + * Return the underlying ASN.1 structure for the certificate in this holder. + * + * @return a X509CertificateStructure object. + */ + public Certificate toASN1Structure() + { + return x509Certificate; + } + + /** + * Return the details of the signature algorithm used to create this attribute certificate. + * + * @return the AlgorithmIdentifier describing the signature algorithm used to create this attribute certificate. + */ + public AlgorithmIdentifier getSignatureAlgorithm() + { + return x509Certificate.getSignatureAlgorithm(); + } + + /** + * Return the bytes making up the signature associated with this attribute certificate. + * + * @return the attribute certificate signature bytes. + */ + public byte[] getSignature() + { + return x509Certificate.getSignature().getBytes(); + } + + /** + * Return whether or not this certificate is valid on a particular date. + * + * @param date the date of interest. + * @return true if the certificate is valid, false otherwise. + */ + public boolean isValidOn(Date date) + { + return !date.before(x509Certificate.getStartDate().getDate()) && !date.after(x509Certificate.getEndDate().getDate()); + } + + /** + * Validate the signature on the certificate 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 CertException if the signature cannot be processed or is inappropriate. + */ + public boolean isSignatureValid(ContentVerifierProvider verifierProvider) + throws CertException + { + TBSCertificate tbsCert = x509Certificate.getTBSCertificate(); + + if (!CertUtils.isAlgIdEqual(tbsCert.getSignature(), x509Certificate.getSignatureAlgorithm())) + { + throw new CertException("signature invalid - algorithm identifier mismatch"); + } + + ContentVerifier verifier; + + try + { + verifier = verifierProvider.get((tbsCert.getSignature())); + + OutputStream sOut = verifier.getOutputStream(); + DEROutputStream dOut = new DEROutputStream(sOut); + + dOut.writeObject(tbsCert); + + sOut.close(); + } + catch (Exception e) + { + throw new CertException("unable to process signature: " + e.getMessage(), e); + } + + return verifier.verify(x509Certificate.getSignature().getBytes()); + } + + public boolean equals( + Object o) + { + if (o == this) + { + return true; + } + + if (!(o instanceof X509CertificateHolder)) + { + return false; + } + + X509CertificateHolder other = (X509CertificateHolder)o; + + return this.x509Certificate.equals(other.x509Certificate); + } + + public int hashCode() + { + return this.x509Certificate.hashCode(); + } + + /** + * Return the ASN.1 encoding of this holder's certificate. + * + * @return a DER encoded byte array. + * @throws IOException if an encoding cannot be generated. + */ + public byte[] getEncoded() + throws IOException + { + return x509Certificate.getEncoded(); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/X509ContentVerifierProviderBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/X509ContentVerifierProviderBuilder.java new file mode 100644 index 000000000..d8429912f --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/X509ContentVerifierProviderBuilder.java @@ -0,0 +1,14 @@ +package org.spongycastle.cert; + +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.operator.ContentVerifierProvider; +import org.spongycastle.operator.OperatorCreationException; + +public interface X509ContentVerifierProviderBuilder +{ + ContentVerifierProvider build(SubjectPublicKeyInfo validatingKeyInfo) + throws OperatorCreationException; + + ContentVerifierProvider build(X509CertificateHolder validatingKeyInfo) + throws OperatorCreationException; +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/X509ExtensionUtils.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/X509ExtensionUtils.java new file mode 100644 index 000000000..54f56adb8 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/X509ExtensionUtils.java @@ -0,0 +1,126 @@ +package org.spongycastle.cert; + +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.x509.AuthorityKeyIdentifier; +import org.spongycastle.asn1.x509.Extension; +import org.spongycastle.asn1.x509.GeneralName; +import org.spongycastle.asn1.x509.GeneralNames; +import org.spongycastle.asn1.x509.SubjectKeyIdentifier; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.operator.DigestCalculator; + +/** + * General utility class for creating calculated extensions using the standard methods. + * <p> + * <b>Note:</b> This class is not thread safe! + * </p> + */ +public class X509ExtensionUtils +{ + private DigestCalculator calculator; + + public X509ExtensionUtils(DigestCalculator calculator) + { + this.calculator = calculator; + } + + public AuthorityKeyIdentifier createAuthorityKeyIdentifier( + X509CertificateHolder certHolder) + { + if (certHolder.getVersionNumber() != 3) + { + GeneralName genName = new GeneralName(certHolder.getIssuer()); + SubjectPublicKeyInfo info = certHolder.getSubjectPublicKeyInfo(); + + return new AuthorityKeyIdentifier( + calculateIdentifier(info), new GeneralNames(genName), certHolder.getSerialNumber()); + } + else + { + GeneralName genName = new GeneralName(certHolder.getIssuer()); + Extension ext = certHolder.getExtension(Extension.subjectKeyIdentifier); + + if (ext != null) + { + ASN1OctetString str = ASN1OctetString.getInstance(ext.getParsedValue()); + + return new AuthorityKeyIdentifier( + str.getOctets(), new GeneralNames(genName), certHolder.getSerialNumber()); + } + else + { + SubjectPublicKeyInfo info = certHolder.getSubjectPublicKeyInfo(); + + return new AuthorityKeyIdentifier( + calculateIdentifier(info), new GeneralNames(genName), certHolder.getSerialNumber()); + } + } + } + + public AuthorityKeyIdentifier createAuthorityKeyIdentifier(SubjectPublicKeyInfo publicKeyInfo) + { + return new AuthorityKeyIdentifier(calculateIdentifier(publicKeyInfo)); + } + + /** + * Return a RFC 3280 type 1 key identifier. As in: + * <pre> + * (1) The keyIdentifier is composed of the 160-bit SHA-1 hash of the + * value of the BIT STRING subjectPublicKey (excluding the tag, + * length, and number of unused bits). + * </pre> + * @param publicKeyInfo the key info object containing the subjectPublicKey field. + * @return the key identifier. + */ + public SubjectKeyIdentifier createSubjectKeyIdentifier( + SubjectPublicKeyInfo publicKeyInfo) + { + return new SubjectKeyIdentifier(calculateIdentifier(publicKeyInfo)); + } + + /** + * Return a RFC 3280 type 2 key identifier. As in: + * <pre> + * (2) The keyIdentifier is composed of a four bit type field with + * the value 0100 followed by the least significant 60 bits of the + * SHA-1 hash of the value of the BIT STRING subjectPublicKey. + * </pre> + * @param publicKeyInfo the key info object containing the subjectPublicKey field. + * @return the key identifier. + */ + public SubjectKeyIdentifier createTruncatedSubjectKeyIdentifier(SubjectPublicKeyInfo publicKeyInfo) + { + byte[] digest = calculateIdentifier(publicKeyInfo); + byte[] id = new byte[8]; + + System.arraycopy(digest, digest.length - 8, id, 0, id.length); + + id[0] &= 0x0f; + id[0] |= 0x40; + + return new SubjectKeyIdentifier(id); + } + + private byte[] calculateIdentifier(SubjectPublicKeyInfo publicKeyInfo) + { + byte[] bytes = publicKeyInfo.getPublicKeyData().getBytes(); + + OutputStream cOut = calculator.getOutputStream(); + + try + { + cOut.write(bytes); + + cOut.close(); + } + catch (IOException e) + { // it's hard to imagine this happening, but yes it does! + throw new CertRuntimeException("unable to calculate identifier: " + e.getMessage(), e); + } + + return calculator.getDigest(); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/X509v1CertificateBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/X509v1CertificateBuilder.java new file mode 100644 index 000000000..b0a9b497f --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/X509v1CertificateBuilder.java @@ -0,0 +1,66 @@ +package org.spongycastle.cert; + +import java.math.BigInteger; +import java.util.Date; + +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.asn1.x509.Time; +import org.spongycastle.asn1.x509.V1TBSCertificateGenerator; +import org.spongycastle.operator.ContentSigner; + + +/** + * class to produce an X.509 Version 1 certificate. + */ +public class X509v1CertificateBuilder +{ + private V1TBSCertificateGenerator tbsGen; + + /** + * Create a builder for a version 1 certificate. + * + * @param issuer the certificate issuer + * @param serial the certificate serial number + * @param notBefore the date before which the certificate is not valid + * @param notAfter the date after which the certificate is not valid + * @param subject the certificate subject + * @param publicKeyInfo the info structure for the public key to be associated with this certificate. + */ + public X509v1CertificateBuilder(X500Name issuer, BigInteger serial, Date notBefore, Date notAfter, X500Name subject, SubjectPublicKeyInfo publicKeyInfo) + { + if (issuer == null) + { + throw new IllegalArgumentException("issuer must not be null"); + } + + if (publicKeyInfo == null) + { + throw new IllegalArgumentException("publicKeyInfo must not be null"); + } + + tbsGen = new V1TBSCertificateGenerator(); + tbsGen.setSerialNumber(new ASN1Integer(serial)); + tbsGen.setIssuer(issuer); + tbsGen.setStartDate(new Time(notBefore)); + tbsGen.setEndDate(new Time(notAfter)); + tbsGen.setSubject(subject); + tbsGen.setSubjectPublicKeyInfo(publicKeyInfo); + } + + /** + * Generate an X509 certificate, based on the current issuer and subject + * using the passed in signer. + * + * @param signer the content signer to be used to generate the signature validating the certificate. + * @return a holder containing the resulting signed certificate. + */ + public X509CertificateHolder build( + ContentSigner signer) + { + tbsGen.setSignature(signer.getAlgorithmIdentifier()); + + return CertUtils.generateFullCert(signer, tbsGen.generateTBSCertificate()); + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/X509v2AttributeCertificateBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/X509v2AttributeCertificateBuilder.java new file mode 100644 index 000000000..199bb9b4d --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/X509v2AttributeCertificateBuilder.java @@ -0,0 +1,109 @@ +package org.spongycastle.cert; + +import java.math.BigInteger; +import java.util.Date; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1GeneralizedTime; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.DERSet; +import org.spongycastle.asn1.x509.AttCertIssuer; +import org.spongycastle.asn1.x509.Attribute; +import org.spongycastle.asn1.x509.ExtensionsGenerator; +import org.spongycastle.asn1.x509.V2AttributeCertificateInfoGenerator; +import org.spongycastle.operator.ContentSigner; + +/** + * class to produce an X.509 Version 2 AttributeCertificate. + */ +public class X509v2AttributeCertificateBuilder +{ + private V2AttributeCertificateInfoGenerator acInfoGen; + private ExtensionsGenerator extGenerator; + + public X509v2AttributeCertificateBuilder(AttributeCertificateHolder holder, AttributeCertificateIssuer issuer, BigInteger serialNumber, Date notBefore, Date notAfter) + { + acInfoGen = new V2AttributeCertificateInfoGenerator(); + extGenerator = new ExtensionsGenerator(); + + acInfoGen.setHolder(holder.holder); + acInfoGen.setIssuer(AttCertIssuer.getInstance(issuer.form)); + acInfoGen.setSerialNumber(new ASN1Integer(serialNumber)); + acInfoGen.setStartDate(new ASN1GeneralizedTime(notBefore)); + acInfoGen.setEndDate(new ASN1GeneralizedTime(notAfter)); + } + + /** + * 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 X509v2AttributeCertificateBuilder addAttribute(ASN1ObjectIdentifier attrType, ASN1Encodable attrValue) + { + acInfoGen.addAttribute(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 X509v2AttributeCertificateBuilder addAttribute(ASN1ObjectIdentifier attrType, ASN1Encodable[] attrValues) + { + acInfoGen.addAttribute(new Attribute(attrType, new DERSet(attrValues))); + + return this; + } + + public void setIssuerUniqueId( + boolean[] iui) + { + acInfoGen.setIssuerUniqueID(CertUtils.booleanToBitString(iui)); + } + + /** + * Add a given extension field for the standard extensions tag + * + * @param oid the OID defining the extension type. + * @param isCritical true if the extension is critical, false otherwise. + * @param value the ASN.1 structure that forms the extension's value. + * @return this builder object. + */ + public X509v2AttributeCertificateBuilder addExtension( + ASN1ObjectIdentifier oid, + boolean isCritical, + ASN1Encodable value) + throws CertIOException + { + CertUtils.addExtension(extGenerator, oid, isCritical, value); + + return this; + } + + /** + * Generate an X509 certificate, based on the current issuer and subject + * using the passed in signer. + * + * @param signer the content signer to be used to generate the signature validating the certificate. + * @return a holder containing the resulting signed certificate. + */ + public X509AttributeCertificateHolder build( + ContentSigner signer) + { + acInfoGen.setSignature(signer.getAlgorithmIdentifier()); + + if (!extGenerator.isEmpty()) + { + acInfoGen.setExtensions(extGenerator.generate()); + } + + return CertUtils.generateFullAttrCert(signer, acInfoGen.generateAttributeCertificateInfo()); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/X509v2CRLBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/X509v2CRLBuilder.java new file mode 100644 index 000000000..d4b211cab --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/X509v2CRLBuilder.java @@ -0,0 +1,182 @@ +package org.spongycastle.cert; + +import java.math.BigInteger; +import java.util.Date; +import java.util.Enumeration; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1GeneralizedTime; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x509.Extensions; +import org.spongycastle.asn1.x509.ExtensionsGenerator; +import org.spongycastle.asn1.x509.TBSCertList; +import org.spongycastle.asn1.x509.Time; +import org.spongycastle.asn1.x509.V2TBSCertListGenerator; +import org.spongycastle.asn1.x509.X509Extensions; +import org.spongycastle.operator.ContentSigner; + +/** + * class to produce an X.509 Version 2 CRL. + */ +public class X509v2CRLBuilder +{ + private V2TBSCertListGenerator tbsGen; + private ExtensionsGenerator extGenerator; + + /** + * Basic constructor. + * + * @param issuer the issuer this CRL is associated with. + * @param thisUpdate the date of this update. + */ + public X509v2CRLBuilder( + X500Name issuer, + Date thisUpdate) + { + tbsGen = new V2TBSCertListGenerator(); + extGenerator = new ExtensionsGenerator(); + + tbsGen.setIssuer(issuer); + tbsGen.setThisUpdate(new Time(thisUpdate)); + } + + /** + * Set the date by which the next CRL will become available. + * + * @param date date of next CRL update. + * @return the current builder. + */ + public X509v2CRLBuilder setNextUpdate( + Date date) + { + tbsGen.setNextUpdate(new Time(date)); + + return this; + } + + /** + * Add a CRL entry with the just reasonCode extension. + * + * @param userCertificateSerial serial number of revoked certificate. + * @param revocationDate date of certificate revocation. + * @param reason the reason code, as indicated in CRLReason, i.e CRLReason.keyCompromise, or 0 if not to be used. + * @return the current builder. + */ + public X509v2CRLBuilder addCRLEntry(BigInteger userCertificateSerial, Date revocationDate, int reason) + { + tbsGen.addCRLEntry(new ASN1Integer(userCertificateSerial), new Time(revocationDate), reason); + + return this; + } + + /** + * Add a CRL entry with an invalidityDate extension as well as a reasonCode extension. This is used + * where the date of revocation might be after issues with the certificate may have occurred. + * + * @param userCertificateSerial serial number of revoked certificate. + * @param revocationDate date of certificate revocation. + * @param reason the reason code, as indicated in CRLReason, i.e CRLReason.keyCompromise, or 0 if not to be used. + * @param invalidityDate the date on which the private key for the certificate became compromised or the certificate otherwise became invalid. + * @return the current builder. + */ + public X509v2CRLBuilder addCRLEntry(BigInteger userCertificateSerial, Date revocationDate, int reason, Date invalidityDate) + { + tbsGen.addCRLEntry(new ASN1Integer(userCertificateSerial), new Time(revocationDate), reason, new ASN1GeneralizedTime(invalidityDate)); + + return this; + } + + /** + * Add a CRL entry with extensions. + * + * @param userCertificateSerial serial number of revoked certificate. + * @param revocationDate date of certificate revocation. + * @param extensions extension set to be associated with this CRLEntry. + * @return the current builder. + * @deprecated use method taking Extensions + */ + public X509v2CRLBuilder addCRLEntry(BigInteger userCertificateSerial, Date revocationDate, X509Extensions extensions) + { + tbsGen.addCRLEntry(new ASN1Integer(userCertificateSerial), new Time(revocationDate), Extensions.getInstance(extensions)); + + return this; + } + + /** + * Add a CRL entry with extensions. + * + * @param userCertificateSerial serial number of revoked certificate. + * @param revocationDate date of certificate revocation. + * @param extensions extension set to be associated with this CRLEntry. + * @return the current builder. + */ + public X509v2CRLBuilder addCRLEntry(BigInteger userCertificateSerial, Date revocationDate, Extensions extensions) + { + tbsGen.addCRLEntry(new ASN1Integer(userCertificateSerial), new Time(revocationDate), extensions); + + return this; + } + + /** + * Add the CRLEntry objects contained in a previous CRL. + * + * @param other the X509CRLHolder to source the other entries from. + * @return the current builder. + */ + public X509v2CRLBuilder addCRL(X509CRLHolder other) + { + TBSCertList revocations = other.toASN1Structure().getTBSCertList(); + + if (revocations != null) + { + for (Enumeration en = revocations.getRevokedCertificateEnumeration(); en.hasMoreElements();) + { + tbsGen.addCRLEntry(ASN1Sequence.getInstance(((ASN1Encodable)en.nextElement()).toASN1Primitive())); + } + } + + return this; + } + + /** + * Add a given extension field for the standard extensions tag (tag 3) + * + * @param oid the OID defining the extension type. + * @param isCritical true if the extension is critical, false otherwise. + * @param value the ASN.1 structure that forms the extension's value. + * @return this builder object. + */ + public X509v2CRLBuilder addExtension( + ASN1ObjectIdentifier oid, + boolean isCritical, + ASN1Encodable value) + throws CertIOException + { + CertUtils.addExtension(extGenerator, oid, isCritical, value); + + return this; + } + + /** + * Generate an X.509 CRL, based on the current issuer and subject + * using the passed in signer. + * + * @param signer the content signer to be used to generate the signature validating the certificate. + * @return a holder containing the resulting signed certificate. + */ + public X509CRLHolder build( + ContentSigner signer) + { + tbsGen.setSignature(signer.getAlgorithmIdentifier()); + + if (!extGenerator.isEmpty()) + { + tbsGen.setExtensions(extGenerator.generate()); + } + + return CertUtils.generateFullCRL(signer, tbsGen.generateTBSCertList()); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/X509v3CertificateBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/X509v3CertificateBuilder.java new file mode 100644 index 000000000..c2082293d --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/X509v3CertificateBuilder.java @@ -0,0 +1,142 @@ +package org.spongycastle.cert; + +import java.math.BigInteger; +import java.util.Date; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x509.Certificate; +import org.spongycastle.asn1.x509.Extension; +import org.spongycastle.asn1.x509.ExtensionsGenerator; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.asn1.x509.Time; +import org.spongycastle.asn1.x509.V3TBSCertificateGenerator; +import org.spongycastle.operator.ContentSigner; + + +/** + * class to produce an X.509 Version 3 certificate. + */ +public class X509v3CertificateBuilder +{ + private V3TBSCertificateGenerator tbsGen; + private ExtensionsGenerator extGenerator; + + /** + * Create a builder for a version 3 certificate. + * + * @param issuer the certificate issuer + * @param serial the certificate serial number + * @param notBefore the date before which the certificate is not valid + * @param notAfter the date after which the certificate is not valid + * @param subject the certificate subject + * @param publicKeyInfo the info structure for the public key to be associated with this certificate. + */ + public X509v3CertificateBuilder(X500Name issuer, BigInteger serial, Date notBefore, Date notAfter, X500Name subject, SubjectPublicKeyInfo publicKeyInfo) + { + tbsGen = new V3TBSCertificateGenerator(); + tbsGen.setSerialNumber(new ASN1Integer(serial)); + tbsGen.setIssuer(issuer); + tbsGen.setStartDate(new Time(notBefore)); + tbsGen.setEndDate(new Time(notAfter)); + tbsGen.setSubject(subject); + tbsGen.setSubjectPublicKeyInfo(publicKeyInfo); + + extGenerator = new ExtensionsGenerator(); + } + + /** + * Set the subjectUniqueID - note: it is very rare that it is correct to do this. + * + * @param uniqueID a boolean array representing the bits making up the subjectUniqueID. + * @return this builder object. + */ + public X509v3CertificateBuilder setSubjectUniqueID(boolean[] uniqueID) + { + tbsGen.setSubjectUniqueID(CertUtils.booleanToBitString(uniqueID)); + + return this; + } + + /** + * Set the issuerUniqueID - note: it is very rare that it is correct to do this. + * + * @param uniqueID a boolean array representing the bits making up the issuerUniqueID. + * @return this builder object. + */ + public X509v3CertificateBuilder setIssuerUniqueID(boolean[] uniqueID) + { + tbsGen.setIssuerUniqueID(CertUtils.booleanToBitString(uniqueID)); + + return this; + } + + /** + * Add a given extension field for the standard extensions tag (tag 3) + * + * @param oid the OID defining the extension type. + * @param isCritical true if the extension is critical, false otherwise. + * @param value the ASN.1 structure that forms the extension's value. + * @return this builder object. + */ + public X509v3CertificateBuilder addExtension( + ASN1ObjectIdentifier oid, + boolean isCritical, + ASN1Encodable value) + throws CertIOException + { + CertUtils.addExtension(extGenerator, oid, isCritical, value); + + return this; + } + + /** + * Add a given extension field for the standard extensions tag (tag 3) + * copying the extension value from another certificate. + * + * @param oid the OID defining the extension type. + * @param isCritical true if the copied extension is to be marked as critical, false otherwise. + * @param certHolder the holder for the certificate that the extension is to be copied from. + * @return this builder object. + */ + public X509v3CertificateBuilder copyAndAddExtension( + ASN1ObjectIdentifier oid, + boolean isCritical, + X509CertificateHolder certHolder) + { + Certificate cert = certHolder.toASN1Structure(); + + Extension extension = cert.getTBSCertificate().getExtensions().getExtension(oid); + + if (extension == null) + { + throw new NullPointerException("extension " + oid + " not present"); + } + + extGenerator.addExtension(oid, isCritical, extension.getExtnValue().getOctets()); + + return this; + } + + /** + * Generate an X.509 certificate, based on the current issuer and subject + * using the passed in signer. + * + * @param signer the content signer to be used to generate the signature validating the certificate. + * @return a holder containing the resulting signed certificate. + */ + public X509CertificateHolder build( + ContentSigner signer) + { + tbsGen.setSignature(signer.getAlgorithmIdentifier()); + + if (!extGenerator.isEmpty()) + { + tbsGen.setExtensions(extGenerator.generate()); + } + + return CertUtils.generateFullCert(signer, tbsGen.generateTBSCertificate()); + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/bc/BcX509ExtensionUtils.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/bc/BcX509ExtensionUtils.java new file mode 100644 index 000000000..6d4dc217d --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/bc/BcX509ExtensionUtils.java @@ -0,0 +1,91 @@ +package org.spongycastle.cert.bc; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.asn1.oiw.OIWObjectIdentifiers; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.AuthorityKeyIdentifier; +import org.spongycastle.asn1.x509.SubjectKeyIdentifier; +import org.spongycastle.cert.X509ExtensionUtils; +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.digests.SHA1Digest; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.crypto.util.SubjectPublicKeyInfoFactory; +import org.spongycastle.operator.DigestCalculator; + +public class BcX509ExtensionUtils + extends X509ExtensionUtils +{ + /** + * Create a utility class pre-configured with a SHA-1 digest calculator based on the + * BC implementation. + */ + public BcX509ExtensionUtils() + { + super(new SHA1DigestCalculator()); + } + + public BcX509ExtensionUtils(DigestCalculator calculator) + { + super(calculator); + } + + public AuthorityKeyIdentifier createAuthorityKeyIdentifier( + AsymmetricKeyParameter publicKey) + throws IOException + { + return super.createAuthorityKeyIdentifier(SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(publicKey)); + } + + /** + * Return a RFC 3280 type 1 key identifier. As in: + * <pre> + * (1) The keyIdentifier is composed of the 160-bit SHA-1 hash of the + * value of the BIT STRING subjectPublicKey (excluding the tag, + * length, and number of unused bits). + * </pre> + * @param publicKey the key object containing the key identifier is to be based on. + * @return the key identifier. + */ + public SubjectKeyIdentifier createSubjectKeyIdentifier( + AsymmetricKeyParameter publicKey) + throws IOException + { + return super.createSubjectKeyIdentifier(SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(publicKey)); + } + + private static class SHA1DigestCalculator + implements DigestCalculator + { + private ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1); + } + + public OutputStream getOutputStream() + { + return bOut; + } + + public byte[] getDigest() + { + byte[] bytes = bOut.toByteArray(); + + bOut.reset(); + + Digest sha1 = new SHA1Digest(); + + sha1.update(bytes, 0, bytes.length); + + byte[] digest = new byte[sha1.getDigestSize()]; + + sha1.doFinal(digest, 0); + + return digest; + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/bc/BcX509v1CertificateBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/bc/BcX509v1CertificateBuilder.java new file mode 100644 index 000000000..2036d5d52 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/bc/BcX509v1CertificateBuilder.java @@ -0,0 +1,33 @@ +package org.spongycastle.cert.bc; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.Date; + +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.cert.X509v1CertificateBuilder; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.crypto.util.SubjectPublicKeyInfoFactory; + +/** + * JCA helper class to allow BC lightweight objects to be used in the construction of a Version 1 certificate. + */ +public class BcX509v1CertificateBuilder + extends X509v1CertificateBuilder +{ + /** + * Initialise the builder using an AsymmetricKeyParameter. + * + * @param issuer X500Name representing the issuer of this certificate. + * @param serial the serial number for the certificate. + * @param notBefore date before which the certificate is not valid. + * @param notAfter date after which the certificate is not valid. + * @param subject X500Name representing the subject of this certificate. + * @param publicKey the public key to be associated with the certificate. + */ + public BcX509v1CertificateBuilder(X500Name issuer, BigInteger serial, Date notBefore, Date notAfter, X500Name subject, AsymmetricKeyParameter publicKey) + throws IOException + { + super(issuer, serial, notBefore, notAfter, subject, SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(publicKey)); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/bc/BcX509v3CertificateBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/bc/BcX509v3CertificateBuilder.java new file mode 100644 index 000000000..282641013 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/bc/BcX509v3CertificateBuilder.java @@ -0,0 +1,51 @@ +package org.spongycastle.cert.bc; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.Date; + +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.cert.X509v3CertificateBuilder; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.crypto.util.SubjectPublicKeyInfoFactory; + +/** + * JCA helper class to allow BC lightweight objects to be used in the construction of a Version 3 certificate. + */ +public class BcX509v3CertificateBuilder + extends X509v3CertificateBuilder +{ + /** + * Initialise the builder using a PublicKey. + * + * @param issuer X500Name representing the issuer of this certificate. + * @param serial the serial number for the certificate. + * @param notBefore date before which the certificate is not valid. + * @param notAfter date after which the certificate is not valid. + * @param subject X500Name representing the subject of this certificate. + * @param publicKey the public key to be associated with the certificate. + */ + public BcX509v3CertificateBuilder(X500Name issuer, BigInteger serial, Date notBefore, Date notAfter, X500Name subject, AsymmetricKeyParameter publicKey) + throws IOException + { + super(issuer, serial, notBefore, notAfter, subject, SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(publicKey)); + } + + /** + * Initialise the builder using the subject from the passed in issuerCert as the issuer, as well as + * passing through and converting the other objects provided. + * + * @param issuerCert holder for certificate who's subject is the issuer of the certificate we are building. + * @param serial the serial number for the certificate. + * @param notBefore date before which the certificate is not valid. + * @param notAfter date after which the certificate is not valid. + * @param subject principal representing the subject of this certificate. + * @param publicKey the public key to be associated with the certificate. + */ + public BcX509v3CertificateBuilder(X509CertificateHolder issuerCert, BigInteger serial, Date notBefore, Date notAfter, X500Name subject, AsymmetricKeyParameter publicKey) + throws IOException + { + super(issuerCert.getSubject(), serial, notBefore, notAfter, subject, SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(publicKey)); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/CMPException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/CMPException.java new file mode 100644 index 000000000..dc02a03bd --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/CMPException.java @@ -0,0 +1,24 @@ +package org.spongycastle.cert.cmp; + +public class CMPException + extends Exception +{ + private Throwable cause; + + public CMPException(String msg, Throwable cause) + { + super(msg); + + this.cause = cause; + } + + public CMPException(String msg) + { + super(msg); + } + + public Throwable getCause() + { + return cause; + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/CMPRuntimeException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/CMPRuntimeException.java new file mode 100644 index 000000000..07f5b819f --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/CMPRuntimeException.java @@ -0,0 +1,19 @@ +package org.spongycastle.cert.cmp; + +public class CMPRuntimeException + extends RuntimeException +{ + private Throwable cause; + + public CMPRuntimeException(String msg, Throwable cause) + { + super(msg); + + this.cause = cause; + } + + public Throwable getCause() + { + return cause; + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/CMPUtil.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/CMPUtil.java new file mode 100644 index 000000000..6198ca84d --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/CMPUtil.java @@ -0,0 +1,26 @@ +package org.spongycastle.cert.cmp; + +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.DEROutputStream; + +class CMPUtil +{ + static void derEncodeToStream(ASN1Encodable obj, OutputStream stream) + { + DEROutputStream dOut = new DEROutputStream(stream); + + try + { + dOut.writeObject(obj); + + dOut.close(); + } + catch (IOException e) + { + throw new CMPRuntimeException("unable to DER encode object: " + e.getMessage(), e); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/CertificateConfirmationContent.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/CertificateConfirmationContent.java new file mode 100644 index 000000000..0f8603414 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/CertificateConfirmationContent.java @@ -0,0 +1,41 @@ +package org.spongycastle.cert.cmp; + +import org.spongycastle.asn1.cmp.CertConfirmContent; +import org.spongycastle.asn1.cmp.CertStatus; +import org.spongycastle.operator.DefaultDigestAlgorithmIdentifierFinder; +import org.spongycastle.operator.DigestAlgorithmIdentifierFinder; + +public class CertificateConfirmationContent +{ + private DigestAlgorithmIdentifierFinder digestAlgFinder; + private CertConfirmContent content; + + public CertificateConfirmationContent(CertConfirmContent content) + { + this(content, new DefaultDigestAlgorithmIdentifierFinder()); + } + + public CertificateConfirmationContent(CertConfirmContent content, DigestAlgorithmIdentifierFinder digestAlgFinder) + { + this.digestAlgFinder = digestAlgFinder; + this.content = content; + } + + public CertConfirmContent toASN1Structure() + { + return content; + } + + public CertificateStatus[] getStatusMessages() + { + CertStatus[] statusArray = content.toCertStatusArray(); + CertificateStatus[] ret = new CertificateStatus[statusArray.length]; + + for (int i = 0; i != ret.length; i++) + { + ret[i] = new CertificateStatus(digestAlgFinder, statusArray[i]); + } + + return ret; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/CertificateConfirmationContentBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/CertificateConfirmationContentBuilder.java new file mode 100644 index 000000000..106b3e24f --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/CertificateConfirmationContentBuilder.java @@ -0,0 +1,78 @@ +package org.spongycastle.cert.cmp; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.asn1.cmp.CertConfirmContent; +import org.spongycastle.asn1.cmp.CertStatus; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.operator.DefaultDigestAlgorithmIdentifierFinder; +import org.spongycastle.operator.DigestAlgorithmIdentifierFinder; +import org.spongycastle.operator.DigestCalculator; +import org.spongycastle.operator.DigestCalculatorProvider; +import org.spongycastle.operator.OperatorCreationException; + +public class CertificateConfirmationContentBuilder +{ + private DigestAlgorithmIdentifierFinder digestAlgFinder; + private List acceptedCerts = new ArrayList(); + private List acceptedReqIds = new ArrayList(); + + public CertificateConfirmationContentBuilder() + { + this(new DefaultDigestAlgorithmIdentifierFinder()); + } + + public CertificateConfirmationContentBuilder(DigestAlgorithmIdentifierFinder digestAlgFinder) + { + this.digestAlgFinder = digestAlgFinder; + } + + public CertificateConfirmationContentBuilder addAcceptedCertificate(X509CertificateHolder certHolder, BigInteger certReqID) + { + acceptedCerts.add(certHolder); + acceptedReqIds.add(certReqID); + + return this; + } + + public CertificateConfirmationContent build(DigestCalculatorProvider digesterProvider) + throws CMPException + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + for (int i = 0; i != acceptedCerts.size(); i++) + { + X509CertificateHolder certHolder = (X509CertificateHolder)acceptedCerts.get(i); + BigInteger reqID = (BigInteger)acceptedReqIds.get(i); + + AlgorithmIdentifier digAlg = digestAlgFinder.find(certHolder.toASN1Structure().getSignatureAlgorithm()); + if (digAlg == null) + { + throw new CMPException("cannot find algorithm for digest from signature"); + } + + DigestCalculator digester; + + try + { + digester = digesterProvider.get(digAlg); + } + catch (OperatorCreationException e) + { + throw new CMPException("unable to create digest: " + e.getMessage(), e); + } + + CMPUtil.derEncodeToStream(certHolder.toASN1Structure(), digester.getOutputStream()); + + v.add(new CertStatus(digester.getDigest(), reqID)); + } + + return new CertificateConfirmationContent(CertConfirmContent.getInstance(new DERSequence(v)), digestAlgFinder); + } + +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/CertificateStatus.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/CertificateStatus.java new file mode 100644 index 000000000..f2923878a --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/CertificateStatus.java @@ -0,0 +1,60 @@ +package org.spongycastle.cert.cmp; + +import java.math.BigInteger; + +import org.spongycastle.asn1.cmp.CertStatus; +import org.spongycastle.asn1.cmp.PKIStatusInfo; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.operator.DigestAlgorithmIdentifierFinder; +import org.spongycastle.operator.DigestCalculator; +import org.spongycastle.operator.DigestCalculatorProvider; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.util.Arrays; + +public class CertificateStatus +{ + private DigestAlgorithmIdentifierFinder digestAlgFinder; + private CertStatus certStatus; + + CertificateStatus(DigestAlgorithmIdentifierFinder digestAlgFinder, CertStatus certStatus) + { + this.digestAlgFinder = digestAlgFinder; + this.certStatus = certStatus; + } + + public PKIStatusInfo getStatusInfo() + { + return certStatus.getStatusInfo(); + } + + public BigInteger getCertRequestID() + { + return certStatus.getCertReqId().getValue(); + } + + public boolean isVerified(X509CertificateHolder certHolder, DigestCalculatorProvider digesterProvider) + throws CMPException + { + AlgorithmIdentifier digAlg = digestAlgFinder.find(certHolder.toASN1Structure().getSignatureAlgorithm()); + if (digAlg == null) + { + throw new CMPException("cannot find algorithm for digest from signature"); + } + + DigestCalculator digester; + + try + { + digester = digesterProvider.get(digAlg); + } + catch (OperatorCreationException e) + { + throw new CMPException("unable to create digester: " + e.getMessage(), e); + } + + CMPUtil.derEncodeToStream(certHolder.toASN1Structure(), digester.getOutputStream()); + + return Arrays.areEqual(certStatus.getCertHash().getOctets(), digester.getDigest()); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/GeneralPKIMessage.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/GeneralPKIMessage.java new file mode 100644 index 000000000..5bd499071 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/GeneralPKIMessage.java @@ -0,0 +1,82 @@ +package org.spongycastle.cert.cmp; + +import java.io.IOException; + +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.cmp.PKIBody; +import org.spongycastle.asn1.cmp.PKIHeader; +import org.spongycastle.asn1.cmp.PKIMessage; +import org.spongycastle.cert.CertIOException; + +/** + * General wrapper for a generic PKIMessage + */ +public class GeneralPKIMessage +{ + private final PKIMessage pkiMessage; + + private static PKIMessage parseBytes(byte[] encoding) + throws IOException + { + try + { + return PKIMessage.getInstance(ASN1Primitive.fromByteArray(encoding)); + } + catch (ClassCastException e) + { + throw new CertIOException("malformed data: " + e.getMessage(), e); + } + catch (IllegalArgumentException e) + { + throw new CertIOException("malformed data: " + e.getMessage(), e); + } + } + + /** + * Create a PKIMessage from the passed in bytes. + * + * @param encoding BER/DER encoding of the PKIMessage + * @throws IOException in the event of corrupted data, or an incorrect structure. + */ + public GeneralPKIMessage(byte[] encoding) + throws IOException + { + this(parseBytes(encoding)); + } + + /** + * Wrap a PKIMessage ASN.1 structure. + * + * @param pkiMessage base PKI message. + */ + public GeneralPKIMessage(PKIMessage pkiMessage) + { + this.pkiMessage = pkiMessage; + } + + public PKIHeader getHeader() + { + return pkiMessage.getHeader(); + } + + public PKIBody getBody() + { + return pkiMessage.getBody(); + } + + /** + * Return true if this message has protection bits on it. A return value of true + * indicates the message can be used to construct a ProtectedPKIMessage. + * + * @return true if message has protection, false otherwise. + */ + public boolean hasProtection() + { + return pkiMessage.getHeader().getProtectionAlg() != null; + } + + public PKIMessage toASN1Structure() + { + return pkiMessage; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/ProtectedPKIMessage.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/ProtectedPKIMessage.java new file mode 100644 index 000000000..b5dbb9804 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/ProtectedPKIMessage.java @@ -0,0 +1,198 @@ +package org.spongycastle.cert.cmp; + +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Encoding; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.asn1.cmp.CMPCertificate; +import org.spongycastle.asn1.cmp.CMPObjectIdentifiers; +import org.spongycastle.asn1.cmp.PBMParameter; +import org.spongycastle.asn1.cmp.PKIBody; +import org.spongycastle.asn1.cmp.PKIHeader; +import org.spongycastle.asn1.cmp.PKIMessage; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.cert.crmf.PKMACBuilder; +import org.spongycastle.operator.ContentVerifier; +import org.spongycastle.operator.ContentVerifierProvider; +import org.spongycastle.operator.MacCalculator; +import org.spongycastle.util.Arrays; + +/** + * Wrapper for a PKIMessage with protection attached to it. + */ +public class ProtectedPKIMessage +{ + private PKIMessage pkiMessage; + + /** + * Base constructor. + * + * @param pkiMessage a GeneralPKIMessage with + */ + public ProtectedPKIMessage(GeneralPKIMessage pkiMessage) + { + if (!pkiMessage.hasProtection()) + { + throw new IllegalArgumentException("PKIMessage not protected"); + } + + this.pkiMessage = pkiMessage.toASN1Structure(); + } + + ProtectedPKIMessage(PKIMessage pkiMessage) + { + if (pkiMessage.getHeader().getProtectionAlg() == null) + { + throw new IllegalArgumentException("PKIMessage not protected"); + } + + this.pkiMessage = pkiMessage; + } + + /** + * Return the message header. + * + * @return the message's PKIHeader structure. + */ + public PKIHeader getHeader() + { + return pkiMessage.getHeader(); + } + + /** + * Return the message body. + * + * @return the message's PKIBody structure. + */ + public PKIBody getBody() + { + return pkiMessage.getBody(); + } + + /** + * Return the underlying ASN.1 structure contained in this object. + * + * @return a PKIMessage structure. + */ + public PKIMessage toASN1Structure() + { + return pkiMessage; + } + + /** + * Determine whether the message is protected by a password based MAC. Use verify(PKMACBuilder, char[]) + * to verify the message if this method returns true. + * + * @return true if protection MAC PBE based, false otherwise. + */ + public boolean hasPasswordBasedMacProtection() + { + return pkiMessage.getHeader().getProtectionAlg().getAlgorithm().equals(CMPObjectIdentifiers.passwordBasedMac); + } + + /** + * Return the extra certificates associated with this message. + * + * @return an array of extra certificates, zero length if none present. + */ + public X509CertificateHolder[] getCertificates() + { + CMPCertificate[] certs = pkiMessage.getExtraCerts(); + + if (certs == null) + { + return new X509CertificateHolder[0]; + } + + X509CertificateHolder[] res = new X509CertificateHolder[certs.length]; + for (int i = 0; i != certs.length; i++) + { + res[i] = new X509CertificateHolder(certs[i].getX509v3PKCert()); + } + + return res; + } + + /** + * Verify a message with a public key based signature attached. + * + * @param verifierProvider a provider of signature verifiers. + * @return true if the provider is able to create a verifier that validates + * the signature, false otherwise. + * @throws CMPException if an exception is thrown trying to verify the signature. + */ + public boolean verify(ContentVerifierProvider verifierProvider) + throws CMPException + { + ContentVerifier verifier; + try + { + verifier = verifierProvider.get(pkiMessage.getHeader().getProtectionAlg()); + + return verifySignature(pkiMessage.getProtection().getBytes(), verifier); + } + catch (Exception e) + { + throw new CMPException("unable to verify signature: " + e.getMessage(), e); + } + } + + /** + * Verify a message with password based MAC protection. + * + * @param pkMacBuilder MAC builder that can be used to construct the appropriate MacCalculator + * @param password the MAC password + * @return true if the passed in password and MAC builder verify the message, false otherwise. + * @throws CMPException if algorithm not MAC based, or an exception is thrown verifying the MAC. + */ + public boolean verify(PKMACBuilder pkMacBuilder, char[] password) + throws CMPException + { + if (!CMPObjectIdentifiers.passwordBasedMac.equals(pkiMessage.getHeader().getProtectionAlg().getAlgorithm())) + { + throw new CMPException("protection algorithm not mac based"); + } + + try + { + pkMacBuilder.setParameters(PBMParameter.getInstance(pkiMessage.getHeader().getProtectionAlg().getParameters())); + MacCalculator calculator = pkMacBuilder.build(password); + + OutputStream macOut = calculator.getOutputStream(); + + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(pkiMessage.getHeader()); + v.add(pkiMessage.getBody()); + + macOut.write(new DERSequence(v).getEncoded(ASN1Encoding.DER)); + + macOut.close(); + + return Arrays.areEqual(calculator.getMac(), pkiMessage.getProtection().getBytes()); + } + catch (Exception e) + { + throw new CMPException("unable to verify MAC: " + e.getMessage(), e); + } + } + + private boolean verifySignature(byte[] signature, ContentVerifier verifier) + throws IOException + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(pkiMessage.getHeader()); + v.add(pkiMessage.getBody()); + + OutputStream sOut = verifier.getOutputStream(); + + sOut.write(new DERSequence(v).getEncoded(ASN1Encoding.DER)); + + sOut.close(); + + return verifier.verify(signature); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/ProtectedPKIMessageBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/ProtectedPKIMessageBuilder.java new file mode 100644 index 000000000..8d5228d17 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/ProtectedPKIMessageBuilder.java @@ -0,0 +1,306 @@ +package org.spongycastle.cert.cmp; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Encoding; +import org.spongycastle.asn1.ASN1GeneralizedTime; +import org.spongycastle.asn1.DERBitString; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.asn1.cmp.CMPCertificate; +import org.spongycastle.asn1.cmp.InfoTypeAndValue; +import org.spongycastle.asn1.cmp.PKIBody; +import org.spongycastle.asn1.cmp.PKIFreeText; +import org.spongycastle.asn1.cmp.PKIHeader; +import org.spongycastle.asn1.cmp.PKIHeaderBuilder; +import org.spongycastle.asn1.cmp.PKIMessage; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.GeneralName; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.operator.ContentSigner; +import org.spongycastle.operator.MacCalculator; + +/** + * Builder for creating a protected PKI message. + */ +public class ProtectedPKIMessageBuilder +{ + private PKIHeaderBuilder hdrBuilder; + private PKIBody body; + private List generalInfos = new ArrayList(); + private List extraCerts = new ArrayList(); + + /** + * Commence a message with the header version CMP_2000. + * + * @param sender message sender. + * @param recipient intended recipient. + */ + public ProtectedPKIMessageBuilder(GeneralName sender, GeneralName recipient) + { + this(PKIHeader.CMP_2000, sender, recipient); + } + + /** + * Commence a message with a specific header type. + * + * @param pvno the version CMP_1999 or CMP_2000. + * @param sender message sender. + * @param recipient intended recipient. + */ + public ProtectedPKIMessageBuilder(int pvno, GeneralName sender, GeneralName recipient) + { + hdrBuilder = new PKIHeaderBuilder(pvno, sender, recipient); + } + + /** + * Set the identifier for the transaction the new message will belong to. + * + * @param tid the transaction ID. + * @return the current builder instance. + */ + public ProtectedPKIMessageBuilder setTransactionID(byte[] tid) + { + hdrBuilder.setTransactionID(tid); + + return this; + } + + /** + * Include a human-readable message in the new message. + * + * @param freeText the contents of the human readable message, + * @return the current builder instance. + */ + public ProtectedPKIMessageBuilder setFreeText(PKIFreeText freeText) + { + hdrBuilder.setFreeText(freeText); + + return this; + } + + /** + * Add a generalInfo data record to the header of the new message. + * + * @param genInfo the generalInfo data to be added. + * @return the current builder instance. + */ + public ProtectedPKIMessageBuilder addGeneralInfo(InfoTypeAndValue genInfo) + { + generalInfos.add(genInfo); + + return this; + } + + /** + * Set the creation time for the new message. + * + * @param time the message creation time. + * @return the current builder instance. + */ + public ProtectedPKIMessageBuilder setMessageTime(Date time) + { + hdrBuilder.setMessageTime(new ASN1GeneralizedTime(time)); + + return this; + } + + /** + * Set the recipient key identifier for the key to be used to verify the new message. + * + * @param kid a key identifier. + * @return the current builder instance. + */ + public ProtectedPKIMessageBuilder setRecipKID(byte[] kid) + { + hdrBuilder.setRecipKID(kid); + + return this; + } + + /** + * Set the recipient nonce field on the new message. + * + * @param nonce a NONCE, typically copied from the sender nonce of the previous message. + * @return the current builder instance. + */ + public ProtectedPKIMessageBuilder setRecipNonce(byte[] nonce) + { + hdrBuilder.setRecipNonce(nonce); + + return this; + } + + /** + * Set the sender key identifier for the key used to protect the new message. + * + * @param kid a key identifier. + * @return the current builder instance. + */ + public ProtectedPKIMessageBuilder setSenderKID(byte[] kid) + { + hdrBuilder.setSenderKID(kid); + + return this; + } + + /** + * Set the sender nonce field on the new message. + * + * @param nonce a NONCE, typically 128 bits of random data. + * @return the current builder instance. + */ + public ProtectedPKIMessageBuilder setSenderNonce(byte[] nonce) + { + hdrBuilder.setSenderNonce(nonce); + + return this; + } + + /** + * Set the body for the new message + * + * @param body the message body. + * @return the current builder instance. + */ + public ProtectedPKIMessageBuilder setBody(PKIBody body) + { + this.body = body; + + return this; + } + + /** + * Add an "extra certificate" to the message. + * + * @param extraCert the extra certificate to add. + * @return the current builder instance. + */ + public ProtectedPKIMessageBuilder addCMPCertificate(X509CertificateHolder extraCert) + { + extraCerts.add(extraCert); + + return this; + } + + /** + * Build a protected PKI message which has MAC based integrity protection. + * + * @param macCalculator MAC calculator. + * @return the resulting protected PKI message. + * @throws CMPException if the protection MAC cannot be calculated. + */ + public ProtectedPKIMessage build(MacCalculator macCalculator) + throws CMPException + { + finaliseHeader(macCalculator.getAlgorithmIdentifier()); + + PKIHeader header = hdrBuilder.build(); + + try + { + DERBitString protection = new DERBitString(calculateMac(macCalculator, header, body)); + + return finaliseMessage(header, protection); + } + catch (IOException e) + { + throw new CMPException("unable to encode MAC input: " + e.getMessage(), e); + } + } + + /** + * Build a protected PKI message which has MAC based integrity protection. + * + * @param signer the ContentSigner to be used to calculate the signature. + * @return the resulting protected PKI message. + * @throws CMPException if the protection signature cannot be calculated. + */ + public ProtectedPKIMessage build(ContentSigner signer) + throws CMPException + { + finaliseHeader(signer.getAlgorithmIdentifier()); + + PKIHeader header = hdrBuilder.build(); + + try + { + DERBitString protection = new DERBitString(calculateSignature(signer, header, body)); + + return finaliseMessage(header, protection); + } + catch (IOException e) + { + throw new CMPException("unable to encode signature input: " + e.getMessage(), e); + } + } + + private void finaliseHeader(AlgorithmIdentifier algorithmIdentifier) + { + hdrBuilder.setProtectionAlg(algorithmIdentifier); + + if (!generalInfos.isEmpty()) + { + InfoTypeAndValue[] genInfos = new InfoTypeAndValue[generalInfos.size()]; + + hdrBuilder.setGeneralInfo((InfoTypeAndValue[])generalInfos.toArray(genInfos)); + } + } + + private ProtectedPKIMessage finaliseMessage(PKIHeader header, DERBitString protection) + { + if (!extraCerts.isEmpty()) + { + CMPCertificate[] cmpCerts = new CMPCertificate[extraCerts.size()]; + + for (int i = 0; i != cmpCerts.length; i++) + { + cmpCerts[i] = new CMPCertificate(((X509CertificateHolder)extraCerts.get(i)).toASN1Structure()); + } + + return new ProtectedPKIMessage(new PKIMessage(header, body, protection, cmpCerts)); + } + else + { + return new ProtectedPKIMessage(new PKIMessage(header, body, protection)); + } + } + + private byte[] calculateSignature(ContentSigner signer, PKIHeader header, PKIBody body) + throws IOException + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(header); + v.add(body); + + OutputStream sOut = signer.getOutputStream(); + + sOut.write(new DERSequence(v).getEncoded(ASN1Encoding.DER)); + + sOut.close(); + + return signer.getSignature(); + } + + private byte[] calculateMac(MacCalculator macCalculator, PKIHeader header, PKIBody body) + throws IOException + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(header); + v.add(body); + + OutputStream sOut = macCalculator.getOutputStream(); + + sOut.write(new DERSequence(v).getEncoded(ASN1Encoding.DER)); + + sOut.close(); + + return macCalculator.getMac(); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/RevocationDetails.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/RevocationDetails.java new file mode 100644 index 000000000..a9c4993c1 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/RevocationDetails.java @@ -0,0 +1,36 @@ +package org.spongycastle.cert.cmp; + +import java.math.BigInteger; + +import org.spongycastle.asn1.cmp.RevDetails; +import org.spongycastle.asn1.x500.X500Name; + +public class RevocationDetails +{ + private RevDetails revDetails; + + public RevocationDetails(RevDetails revDetails) + { + this.revDetails = revDetails; + } + + public X500Name getSubject() + { + return revDetails.getCertDetails().getSubject(); + } + + public X500Name getIssuer() + { + return revDetails.getCertDetails().getIssuer(); + } + + public BigInteger getSerialNumber() + { + return revDetails.getCertDetails().getSerialNumber().getValue(); + } + + public RevDetails toASN1Structure() + { + return revDetails; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/RevocationDetailsBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/RevocationDetailsBuilder.java new file mode 100644 index 000000000..bc7eaa042 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/cmp/RevocationDetailsBuilder.java @@ -0,0 +1,59 @@ +package org.spongycastle.cert.cmp; + +import java.math.BigInteger; + +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.cmp.RevDetails; +import org.spongycastle.asn1.crmf.CertTemplateBuilder; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; + +public class RevocationDetailsBuilder +{ + private CertTemplateBuilder templateBuilder = new CertTemplateBuilder(); + + public RevocationDetailsBuilder setPublicKey(SubjectPublicKeyInfo publicKey) + { + if (publicKey != null) + { + templateBuilder.setPublicKey(publicKey); + } + + return this; + } + + public RevocationDetailsBuilder setIssuer(X500Name issuer) + { + if (issuer != null) + { + templateBuilder.setIssuer(issuer); + } + + return this; + } + + public RevocationDetailsBuilder setSerialNumber(BigInteger serialNumber) + { + if (serialNumber != null) + { + templateBuilder.setSerialNumber(new ASN1Integer(serialNumber)); + } + + return this; + } + + public RevocationDetailsBuilder setSubject(X500Name subject) + { + if (subject != null) + { + templateBuilder.setSubject(subject); + } + + return this; + } + + public RevocationDetails build() + { + return new RevocationDetails(new RevDetails(templateBuilder.build())); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/AuthenticatorControl.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/AuthenticatorControl.java new file mode 100644 index 000000000..58e6c63a5 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/AuthenticatorControl.java @@ -0,0 +1,57 @@ +package org.spongycastle.cert.crmf; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.DERUTF8String; +import org.spongycastle.asn1.crmf.CRMFObjectIdentifiers; + +/** + * Carrier for an authenticator control. + */ +public class AuthenticatorControl + implements Control +{ + private static final ASN1ObjectIdentifier type = CRMFObjectIdentifiers.id_regCtrl_authenticator; + + private final DERUTF8String token; + + /** + * Basic constructor - build from a UTF-8 string representing the token. + * + * @param token UTF-8 string representing the token. + */ + public AuthenticatorControl(DERUTF8String token) + { + this.token = token; + } + + /** + * Basic constructor - build from a string representing the token. + * + * @param token string representing the token. + */ + public AuthenticatorControl(String token) + { + this.token = new DERUTF8String(token); + } + + /** + * Return the type of this control. + * + * @return CRMFObjectIdentifiers.id_regCtrl_authenticator + */ + public ASN1ObjectIdentifier getType() + { + return type; + } + + /** + * Return the token associated with this control (a UTF8String). + * + * @return a UTF8String. + */ + public ASN1Encodable getValue() + { + return token; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/CRMFException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/CRMFException.java new file mode 100644 index 000000000..14aa0ad2b --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/CRMFException.java @@ -0,0 +1,19 @@ +package org.spongycastle.cert.crmf; + +public class CRMFException + extends Exception +{ + private Throwable cause; + + public CRMFException(String msg, Throwable cause) + { + super(msg); + + this.cause = cause; + } + + public Throwable getCause() + { + return cause; + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/CRMFRuntimeException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/CRMFRuntimeException.java new file mode 100644 index 000000000..cde484c4c --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/CRMFRuntimeException.java @@ -0,0 +1,19 @@ +package org.spongycastle.cert.crmf; + +public class CRMFRuntimeException + extends RuntimeException +{ + private Throwable cause; + + public CRMFRuntimeException(String msg, Throwable cause) + { + super(msg); + + this.cause = cause; + } + + public Throwable getCause() + { + return cause; + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/CRMFUtil.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/CRMFUtil.java new file mode 100644 index 000000000..eb6771e62 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/CRMFUtil.java @@ -0,0 +1,42 @@ +package org.spongycastle.cert.crmf; + +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.DEROutputStream; +import org.spongycastle.asn1.x509.ExtensionsGenerator; +import org.spongycastle.cert.CertIOException; + +class CRMFUtil +{ + static void derEncodeToStream(ASN1Encodable obj, OutputStream stream) + { + DEROutputStream dOut = new DEROutputStream(stream); + + try + { + dOut.writeObject(obj); + + dOut.close(); + } + catch (IOException e) + { + throw new CRMFRuntimeException("unable to DER encode object: " + e.getMessage(), e); + } + } + + static void addExtension(ExtensionsGenerator extGenerator, ASN1ObjectIdentifier oid, boolean isCritical, ASN1Encodable value) + throws CertIOException + { + try + { + extGenerator.addExtension(oid, isCritical, value); + } + catch (IOException e) + { + throw new CertIOException("cannot encode extension: " + e.getMessage(), e); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/CertificateRequestMessage.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/CertificateRequestMessage.java new file mode 100644 index 000000000..987b6b376 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/CertificateRequestMessage.java @@ -0,0 +1,309 @@ +package org.spongycastle.cert.crmf; + +import java.io.IOException; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.DERUTF8String; +import org.spongycastle.asn1.crmf.AttributeTypeAndValue; +import org.spongycastle.asn1.crmf.CRMFObjectIdentifiers; +import org.spongycastle.asn1.crmf.CertReqMsg; +import org.spongycastle.asn1.crmf.CertTemplate; +import org.spongycastle.asn1.crmf.Controls; +import org.spongycastle.asn1.crmf.PKIArchiveOptions; +import org.spongycastle.asn1.crmf.PKMACValue; +import org.spongycastle.asn1.crmf.POPOSigningKey; +import org.spongycastle.asn1.crmf.ProofOfPossession; +import org.spongycastle.cert.CertIOException; +import org.spongycastle.operator.ContentVerifier; +import org.spongycastle.operator.ContentVerifierProvider; +import org.spongycastle.operator.OperatorCreationException; + +/** + * Carrier for a CRMF CertReqMsg. + */ +public class CertificateRequestMessage +{ + public static final int popRaVerified = ProofOfPossession.TYPE_RA_VERIFIED; + public static final int popSigningKey = ProofOfPossession.TYPE_SIGNING_KEY; + public static final int popKeyEncipherment = ProofOfPossession.TYPE_KEY_ENCIPHERMENT; + public static final int popKeyAgreement = ProofOfPossession.TYPE_KEY_AGREEMENT; + + private final CertReqMsg certReqMsg; + private final Controls controls; + + private static CertReqMsg parseBytes(byte[] encoding) + throws IOException + { + try + { + return CertReqMsg.getInstance(ASN1Primitive.fromByteArray(encoding)); + } + catch (ClassCastException e) + { + throw new CertIOException("malformed data: " + e.getMessage(), e); + } + catch (IllegalArgumentException e) + { + throw new CertIOException("malformed data: " + e.getMessage(), e); + } + } + + /** + * Create a CertificateRequestMessage from the passed in bytes. + * + * @param certReqMsg BER/DER encoding of the CertReqMsg structure. + * @throws IOException in the event of corrupted data, or an incorrect structure. + */ + public CertificateRequestMessage(byte[] certReqMsg) + throws IOException + { + this(parseBytes(certReqMsg)); + } + + public CertificateRequestMessage(CertReqMsg certReqMsg) + { + this.certReqMsg = certReqMsg; + this.controls = certReqMsg.getCertReq().getControls(); + } + + /** + * Return the underlying ASN.1 object defining this CertificateRequestMessage object. + * + * @return a CertReqMsg. + */ + public CertReqMsg toASN1Structure() + { + return certReqMsg; + } + + /** + * Return the certificate template contained in this message. + * + * @return a CertTemplate structure. + */ + public CertTemplate getCertTemplate() + { + return this.certReqMsg.getCertReq().getCertTemplate(); + } + + /** + * Return whether or not this request has control values associated with it. + * + * @return true if there are control values present, false otherwise. + */ + public boolean hasControls() + { + return controls != null; + } + + /** + * Return whether or not this request has a specific type of control value. + * + * @param type the type OID for the control value we are checking for. + * @return true if a control value of type is present, false otherwise. + */ + public boolean hasControl(ASN1ObjectIdentifier type) + { + return findControl(type) != null; + } + + /** + * Return a control value of the specified type. + * + * @param type the type OID for the control value we are checking for. + * @return the control value if present, null otherwise. + */ + public Control getControl(ASN1ObjectIdentifier type) + { + AttributeTypeAndValue found = findControl(type); + + if (found != null) + { + if (found.getType().equals(CRMFObjectIdentifiers.id_regCtrl_pkiArchiveOptions)) + { + return new PKIArchiveControl(PKIArchiveOptions.getInstance(found.getValue())); + } + if (found.getType().equals(CRMFObjectIdentifiers.id_regCtrl_regToken)) + { + return new RegTokenControl(DERUTF8String.getInstance(found.getValue())); + } + if (found.getType().equals(CRMFObjectIdentifiers.id_regCtrl_authenticator)) + { + return new AuthenticatorControl(DERUTF8String.getInstance(found.getValue())); + } + } + + return null; + } + + private AttributeTypeAndValue findControl(ASN1ObjectIdentifier type) + { + if (controls == null) + { + return null; + } + + AttributeTypeAndValue[] tAndVs = controls.toAttributeTypeAndValueArray(); + AttributeTypeAndValue found = null; + + for (int i = 0; i != tAndVs.length; i++) + { + if (tAndVs[i].getType().equals(type)) + { + found = tAndVs[i]; + break; + } + } + + return found; + } + + /** + * Return whether or not this request message has a proof-of-possession field in it. + * + * @return true if proof-of-possession is present, false otherwise. + */ + public boolean hasProofOfPossession() + { + return this.certReqMsg.getPopo() != null; + } + + /** + * Return the type of the proof-of-possession this request message provides. + * + * @return one of: popRaVerified, popSigningKey, popKeyEncipherment, popKeyAgreement + */ + public int getProofOfPossessionType() + { + return this.certReqMsg.getPopo().getType(); + } + + /** + * Return whether or not the proof-of-possession (POP) is of the type popSigningKey and + * it has a public key MAC associated with it. + * + * @return true if POP is popSigningKey and a PKMAC is present, false otherwise. + */ + public boolean hasSigningKeyProofOfPossessionWithPKMAC() + { + ProofOfPossession pop = certReqMsg.getPopo(); + + if (pop.getType() == popSigningKey) + { + POPOSigningKey popoSign = POPOSigningKey.getInstance(pop.getObject()); + + return popoSign.getPoposkInput().getPublicKeyMAC() != null; + } + + return false; + } + + /** + * Return whether or not a signing key proof-of-possession (POP) is valid. + * + * @param verifierProvider a provider that can produce content verifiers for the signature contained in this POP. + * @return true if the POP is valid, false otherwise. + * @throws CRMFException if there is a problem in verification or content verifier creation. + * @throws IllegalStateException if POP not appropriate. + */ + public boolean isValidSigningKeyPOP(ContentVerifierProvider verifierProvider) + throws CRMFException, IllegalStateException + { + ProofOfPossession pop = certReqMsg.getPopo(); + + if (pop.getType() == popSigningKey) + { + POPOSigningKey popoSign = POPOSigningKey.getInstance(pop.getObject()); + + if (popoSign.getPoposkInput() != null && popoSign.getPoposkInput().getPublicKeyMAC() != null) + { + throw new IllegalStateException("verification requires password check"); + } + + return verifySignature(verifierProvider, popoSign); + } + else + { + throw new IllegalStateException("not Signing Key type of proof of possession"); + } + } + + /** + * Return whether or not a signing key proof-of-possession (POP), with an associated PKMAC, is valid. + * + * @param verifierProvider a provider that can produce content verifiers for the signature contained in this POP. + * @param macBuilder a suitable PKMACBuilder to create the MAC verifier. + * @param password the password used to key the MAC calculation. + * @return true if the POP is valid, false otherwise. + * @throws CRMFException if there is a problem in verification or content verifier creation. + * @throws IllegalStateException if POP not appropriate. + */ + public boolean isValidSigningKeyPOP(ContentVerifierProvider verifierProvider, PKMACBuilder macBuilder, char[] password) + throws CRMFException, IllegalStateException + { + ProofOfPossession pop = certReqMsg.getPopo(); + + if (pop.getType() == popSigningKey) + { + POPOSigningKey popoSign = POPOSigningKey.getInstance(pop.getObject()); + + if (popoSign.getPoposkInput() == null || popoSign.getPoposkInput().getSender() != null) + { + throw new IllegalStateException("no PKMAC present in proof of possession"); + } + + PKMACValue pkMAC = popoSign.getPoposkInput().getPublicKeyMAC(); + PKMACValueVerifier macVerifier = new PKMACValueVerifier(macBuilder); + + if (macVerifier.isValid(pkMAC, password, this.getCertTemplate().getPublicKey())) + { + return verifySignature(verifierProvider, popoSign); + } + + return false; + } + else + { + throw new IllegalStateException("not Signing Key type of proof of possession"); + } + } + + private boolean verifySignature(ContentVerifierProvider verifierProvider, POPOSigningKey popoSign) + throws CRMFException + { + ContentVerifier verifier; + + try + { + verifier = verifierProvider.get(popoSign.getAlgorithmIdentifier()); + } + catch (OperatorCreationException e) + { + throw new CRMFException("unable to create verifier: " + e.getMessage(), e); + } + + if (popoSign.getPoposkInput() != null) + { + CRMFUtil.derEncodeToStream(popoSign.getPoposkInput(), verifier.getOutputStream()); + } + else + { + CRMFUtil.derEncodeToStream(certReqMsg.getCertReq(), verifier.getOutputStream()); + } + + return verifier.verify(popoSign.getSignature().getBytes()); + } + + /** + * Return the ASN.1 encoding of the certReqMsg we wrap. + * + * @return a byte array containing the binary encoding of the certReqMsg. + * @throws IOException if there is an exception creating the encoding. + */ + public byte[] getEncoded() + throws IOException + { + return certReqMsg.getEncoded(); + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/CertificateRequestMessageBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/CertificateRequestMessageBuilder.java new file mode 100644 index 000000000..4bfd6451a --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/CertificateRequestMessageBuilder.java @@ -0,0 +1,279 @@ +package org.spongycastle.cert.crmf; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.ASN1Null; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.DERNull; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.asn1.crmf.AttributeTypeAndValue; +import org.spongycastle.asn1.crmf.CertReqMsg; +import org.spongycastle.asn1.crmf.CertRequest; +import org.spongycastle.asn1.crmf.CertTemplate; +import org.spongycastle.asn1.crmf.CertTemplateBuilder; +import org.spongycastle.asn1.crmf.OptionalValidity; +import org.spongycastle.asn1.crmf.POPOPrivKey; +import org.spongycastle.asn1.crmf.ProofOfPossession; +import org.spongycastle.asn1.crmf.SubsequentMessage; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x509.ExtensionsGenerator; +import org.spongycastle.asn1.x509.GeneralName; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.asn1.x509.Time; +import org.spongycastle.cert.CertIOException; +import org.spongycastle.operator.ContentSigner; + +public class CertificateRequestMessageBuilder +{ + private final BigInteger certReqId; + + private ExtensionsGenerator extGenerator; + private CertTemplateBuilder templateBuilder; + private List controls; + private ContentSigner popSigner; + private PKMACBuilder pkmacBuilder; + private char[] password; + private GeneralName sender; + private POPOPrivKey popoPrivKey; + private ASN1Null popRaVerified; + + public CertificateRequestMessageBuilder(BigInteger certReqId) + { + this.certReqId = certReqId; + + this.extGenerator = new ExtensionsGenerator(); + this.templateBuilder = new CertTemplateBuilder(); + this.controls = new ArrayList(); + } + + public CertificateRequestMessageBuilder setPublicKey(SubjectPublicKeyInfo publicKey) + { + if (publicKey != null) + { + templateBuilder.setPublicKey(publicKey); + } + + return this; + } + + public CertificateRequestMessageBuilder setIssuer(X500Name issuer) + { + if (issuer != null) + { + templateBuilder.setIssuer(issuer); + } + + return this; + } + + public CertificateRequestMessageBuilder setSubject(X500Name subject) + { + if (subject != null) + { + templateBuilder.setSubject(subject); + } + + return this; + } + + public CertificateRequestMessageBuilder setSerialNumber(BigInteger serialNumber) + { + if (serialNumber != null) + { + templateBuilder.setSerialNumber(new ASN1Integer(serialNumber)); + } + + return this; + } + + /** + * Request a validity period for the certificate. Either, but not both, of the date parameters may be null. + * + * @param notBeforeDate not before date for certificate requested. + * @param notAfterDate not after date for the certificate requested. + * + * @return the current builder. + */ + public CertificateRequestMessageBuilder setValidity(Date notBeforeDate, Date notAfterDate) + { + templateBuilder.setValidity(new OptionalValidity(createTime(notBeforeDate), createTime(notAfterDate))); + + return this; + } + + private Time createTime(Date date) + { + if (date != null) + { + return new Time(date); + } + + return null; + } + + public CertificateRequestMessageBuilder addExtension( + ASN1ObjectIdentifier oid, + boolean critical, + ASN1Encodable value) + throws CertIOException + { + CRMFUtil.addExtension(extGenerator, oid, critical, value); + + return this; + } + + public CertificateRequestMessageBuilder addExtension( + ASN1ObjectIdentifier oid, + boolean critical, + byte[] value) + { + extGenerator.addExtension(oid, critical, value); + + return this; + } + + public CertificateRequestMessageBuilder addControl(Control control) + { + controls.add(control); + + return this; + } + + public CertificateRequestMessageBuilder setProofOfPossessionSigningKeySigner(ContentSigner popSigner) + { + if (popoPrivKey != null || popRaVerified != null) + { + throw new IllegalStateException("only one proof of possession allowed"); + } + + this.popSigner = popSigner; + + return this; + } + + public CertificateRequestMessageBuilder setProofOfPossessionSubsequentMessage(SubsequentMessage msg) + { + if (popSigner != null || popRaVerified != null) + { + throw new IllegalStateException("only one proof of possession allowed"); + } + + this.popoPrivKey = new POPOPrivKey(msg); + + return this; + } + + public CertificateRequestMessageBuilder setProofOfPossessionRaVerified() + { + if (popSigner != null || popoPrivKey != null) + { + throw new IllegalStateException("only one proof of possession allowed"); + } + + this.popRaVerified = DERNull.INSTANCE; + + return this; + } + + public CertificateRequestMessageBuilder setAuthInfoPKMAC(PKMACBuilder pkmacBuilder, char[] password) + { + this.pkmacBuilder = pkmacBuilder; + this.password = password; + + return this; + } + + public CertificateRequestMessageBuilder setAuthInfoSender(X500Name sender) + { + return setAuthInfoSender(new GeneralName(sender)); + } + + public CertificateRequestMessageBuilder setAuthInfoSender(GeneralName sender) + { + this.sender = sender; + + return this; + } + + public CertificateRequestMessage build() + throws CRMFException + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(new ASN1Integer(certReqId)); + + if (!extGenerator.isEmpty()) + { + templateBuilder.setExtensions(extGenerator.generate()); + } + + v.add(templateBuilder.build()); + + if (!controls.isEmpty()) + { + ASN1EncodableVector controlV = new ASN1EncodableVector(); + + for (Iterator it = controls.iterator(); it.hasNext();) + { + Control control = (Control)it.next(); + + controlV.add(new AttributeTypeAndValue(control.getType(), control.getValue())); + } + + v.add(new DERSequence(controlV)); + } + + CertRequest request = CertRequest.getInstance(new DERSequence(v)); + + v = new ASN1EncodableVector(); + + v.add(request); + + if (popSigner != null) + { + CertTemplate template = request.getCertTemplate(); + + if (template.getSubject() == null || template.getPublicKey() == null) + { + SubjectPublicKeyInfo pubKeyInfo = request.getCertTemplate().getPublicKey(); + ProofOfPossessionSigningKeyBuilder builder = new ProofOfPossessionSigningKeyBuilder(pubKeyInfo); + + if (sender != null) + { + builder.setSender(sender); + } + else + { + PKMACValueGenerator pkmacGenerator = new PKMACValueGenerator(pkmacBuilder); + + builder.setPublicKeyMac(pkmacGenerator, password); + } + + v.add(new ProofOfPossession(builder.build(popSigner))); + } + else + { + ProofOfPossessionSigningKeyBuilder builder = new ProofOfPossessionSigningKeyBuilder(request); + + v.add(new ProofOfPossession(builder.build(popSigner))); + } + } + else if (popoPrivKey != null) + { + v.add(new ProofOfPossession(ProofOfPossession.TYPE_KEY_ENCIPHERMENT, popoPrivKey)); + } + else if (popRaVerified != null) + { + v.add(new ProofOfPossession()); + } + + return new CertificateRequestMessage(CertReqMsg.getInstance(new DERSequence(v))); + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/Control.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/Control.java new file mode 100644 index 000000000..4454f7e77 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/Control.java @@ -0,0 +1,24 @@ +package org.spongycastle.cert.crmf; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1ObjectIdentifier; + +/** + * Generic interface for a CertificateRequestMessage control value. + */ +public interface Control +{ + /** + * Return the type of this control. + * + * @return an ASN1ObjectIdentifier representing the type. + */ + ASN1ObjectIdentifier getType(); + + /** + * Return the value contained in this control object. + * + * @return the value of the control. + */ + ASN1Encodable getValue(); +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/EncryptedValueBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/EncryptedValueBuilder.java new file mode 100644 index 000000000..6a34f49ce --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/EncryptedValueBuilder.java @@ -0,0 +1,133 @@ +package org.spongycastle.cert.crmf; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.DERBitString; +import org.spongycastle.asn1.crmf.EncryptedValue; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.operator.KeyWrapper; +import org.spongycastle.operator.OperatorException; +import org.spongycastle.operator.OutputEncryptor; +import org.spongycastle.util.Strings; + +/** + * Builder for EncryptedValue structures. + */ +public class EncryptedValueBuilder +{ + private KeyWrapper wrapper; + private OutputEncryptor encryptor; + private EncryptedValuePadder padder; + + /** + * Create a builder that makes EncryptedValue structures. + * + * @param wrapper a wrapper for key used to encrypt the actual data contained in the EncryptedValue. + * @param encryptor an output encryptor to encrypt the actual data contained in the EncryptedValue. + */ + public EncryptedValueBuilder(KeyWrapper wrapper, OutputEncryptor encryptor) + { + this(wrapper, encryptor, null); + } + + /** + * Create a builder that makes EncryptedValue structures with fixed length blocks padded using the passed in padder. + * + * @param wrapper a wrapper for key used to encrypt the actual data contained in the EncryptedValue. + * @param encryptor an output encryptor to encrypt the actual data contained in the EncryptedValue. + * @param padder a padder to ensure that the EncryptedValue created will always be a constant length. + */ + public EncryptedValueBuilder(KeyWrapper wrapper, OutputEncryptor encryptor, EncryptedValuePadder padder) + { + this.wrapper = wrapper; + this.encryptor = encryptor; + this.padder = padder; + } + + /** + * Build an EncryptedValue structure containing the passed in pass phrase. + * + * @param revocationPassphrase a revocation pass phrase. + * @return an EncryptedValue containing the encrypted pass phrase. + * @throws CRMFException on a failure to encrypt the data, or wrap the symmetric key for this value. + */ + public EncryptedValue build(char[] revocationPassphrase) + throws CRMFException + { + return encryptData(padData(Strings.toUTF8ByteArray(revocationPassphrase))); + } + + /** + * Build an EncryptedValue structure containing the certificate contained in + * the passed in holder. + * + * @param holder a holder containing a certificate. + * @return an EncryptedValue containing the encrypted certificate. + * @throws CRMFException on a failure to encrypt the data, or wrap the symmetric key for this value. + */ + public EncryptedValue build(X509CertificateHolder holder) + throws CRMFException + { + try + { + return encryptData(padData(holder.getEncoded())); + } + catch (IOException e) + { + throw new CRMFException("cannot encode certificate: " + e.getMessage(), e); + } + } + + private EncryptedValue encryptData(byte[] data) + throws CRMFException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + OutputStream eOut = encryptor.getOutputStream(bOut); + + try + { + eOut.write(data); + + eOut.close(); + } + catch (IOException e) + { + throw new CRMFException("cannot process data: " + e.getMessage(), e); + } + + AlgorithmIdentifier intendedAlg = null; + AlgorithmIdentifier symmAlg = encryptor.getAlgorithmIdentifier(); + DERBitString encSymmKey; + + try + { + wrapper.generateWrappedKey(encryptor.getKey()); + encSymmKey = new DERBitString(wrapper.generateWrappedKey(encryptor.getKey())); + } + catch (OperatorException e) + { + throw new CRMFException("cannot wrap key: " + e.getMessage(), e); + } + + AlgorithmIdentifier keyAlg = wrapper.getAlgorithmIdentifier(); + ASN1OctetString valueHint = null; + DERBitString encValue = new DERBitString(bOut.toByteArray()); + + return new EncryptedValue(intendedAlg, symmAlg, encSymmKey, keyAlg, valueHint, encValue); + } + + private byte[] padData(byte[] data) + { + if (padder != null) + { + return padder.getPaddedData(data); + } + + return data; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/EncryptedValuePadder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/EncryptedValuePadder.java new file mode 100644 index 000000000..d00b5b62c --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/EncryptedValuePadder.java @@ -0,0 +1,24 @@ +package org.spongycastle.cert.crmf; + +/** + * An encrypted value padder is used to make sure that prior to a value been + * encrypted the data is padded to a standard length. + */ +public interface EncryptedValuePadder +{ + /** + * Return a byte array of padded data. + * + * @param data the data to be padded. + * @return a padded byte array containing data. + */ + byte[] getPaddedData(byte[] data); + + /** + * Return a byte array of with padding removed. + * + * @param paddedData the data to be padded. + * @return an array containing the original unpadded data. + */ + byte[] getUnpaddedData(byte[] paddedData); +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/EncryptedValueParser.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/EncryptedValueParser.java new file mode 100644 index 000000000..80804993a --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/EncryptedValueParser.java @@ -0,0 +1,103 @@ +package org.spongycastle.cert.crmf; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.spongycastle.asn1.crmf.EncryptedValue; +import org.spongycastle.asn1.x509.Certificate; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.operator.InputDecryptor; +import org.spongycastle.util.Strings; +import org.spongycastle.util.io.Streams; + +/** + * Parser for EncryptedValue structures. + */ +public class EncryptedValueParser +{ + private EncryptedValue value; + private EncryptedValuePadder padder; + + /** + * Basic constructor - create a parser to read the passed in value. + * + * @param value the value to be parsed. + */ + public EncryptedValueParser(EncryptedValue value) + { + this.value = value; + } + + /** + * Create a parser to read the passed in value, assuming the padder was + * applied to the data prior to encryption. + * + * @param value the value to be parsed. + * @param padder the padder to be used to remove padding from the decrypted value.. + */ + public EncryptedValueParser(EncryptedValue value, EncryptedValuePadder padder) + { + this.value = value; + this.padder = padder; + } + + private byte[] decryptValue(ValueDecryptorGenerator decGen) + throws CRMFException + { + if (value.getIntendedAlg() != null) + { + throw new UnsupportedOperationException(); + } + if (value.getValueHint() != null) + { + throw new UnsupportedOperationException(); + } + + InputDecryptor decryptor = decGen.getValueDecryptor(value.getKeyAlg(), + value.getSymmAlg(), value.getEncSymmKey().getBytes()); + InputStream dataIn = decryptor.getInputStream(new ByteArrayInputStream( + value.getEncValue().getBytes())); + try + { + byte[] data = Streams.readAll(dataIn); + + if (padder != null) + { + return padder.getUnpaddedData(data); + } + + return data; + } + catch (IOException e) + { + throw new CRMFException("Cannot parse decrypted data: " + e.getMessage(), e); + } + } + + /** + * Read a X.509 certificate. + * + * @param decGen the decryptor generator to decrypt the encrypted value. + * @return an X509CertificateHolder containing the certificate read. + * @throws CRMFException if the decrypted data cannot be parsed, or a decryptor cannot be generated. + */ + public X509CertificateHolder readCertificateHolder(ValueDecryptorGenerator decGen) + throws CRMFException + { + return new X509CertificateHolder(Certificate.getInstance(decryptValue(decGen))); + } + + /** + * Read a pass phrase. + * + * @param decGen the decryptor generator to decrypt the encrypted value. + * @return a pass phrase as recovered from the encrypted value. + * @throws CRMFException if the decrypted data cannot be parsed, or a decryptor cannot be generated. + */ + public char[] readPassphrase(ValueDecryptorGenerator decGen) + throws CRMFException + { + return Strings.fromUTF8ByteArray(decryptValue(decGen)).toCharArray(); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/FixedLengthMGF1Padder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/FixedLengthMGF1Padder.java new file mode 100644 index 000000000..5dd1ce30f --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/FixedLengthMGF1Padder.java @@ -0,0 +1,120 @@ +package org.spongycastle.cert.crmf; + +import java.security.SecureRandom; + +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.digests.SHA1Digest; +import org.spongycastle.crypto.generators.MGF1BytesGenerator; +import org.spongycastle.crypto.params.MGFParameters; + +/** + * An encrypted value padder that uses MGF1 as the basis of the padding. + */ +public class FixedLengthMGF1Padder + implements EncryptedValuePadder +{ + private int length; + private SecureRandom random; + private Digest dig = new SHA1Digest(); + + /** + * Create a padder to so that padded output will always be at least + * length bytes long. + * + * @param length fixed length for padded output. + */ + public FixedLengthMGF1Padder(int length) + { + this(length, null); + } + + /** + * Create a padder to so that padded output will always be at least + * length bytes long, using the passed in source of randomness to + * provide the random material for the padder. + * + * @param length fixed length for padded output. + * @param random a source of randomness. + */ + public FixedLengthMGF1Padder(int length, SecureRandom random) + { + this.length = length; + this.random = random; + } + + public byte[] getPaddedData(byte[] data) + { + byte[] bytes = new byte[length]; + byte[] seed = new byte[dig.getDigestSize()]; + byte[] mask = new byte[length - dig.getDigestSize()]; + + if (random == null) + { + random = new SecureRandom(); + } + + random.nextBytes(seed); + + MGF1BytesGenerator maskGen = new MGF1BytesGenerator(dig); + + maskGen.init(new MGFParameters(seed)); + + maskGen.generateBytes(mask, 0, mask.length); + + System.arraycopy(seed, 0, bytes, 0, seed.length); + System.arraycopy(data, 0, bytes, seed.length, data.length); + + for (int i = seed.length + data.length + 1; i != bytes.length; i++) + { + bytes[i] = (byte)(1 + random.nextInt(255)); + } + + for (int i = 0; i != mask.length; i++) + { + bytes[i + seed.length] ^= mask[i]; + } + + return bytes; + } + + public byte[] getUnpaddedData(byte[] paddedData) + { + byte[] seed = new byte[dig.getDigestSize()]; + byte[] mask = new byte[length - dig.getDigestSize()]; + + System.arraycopy(paddedData, 0, seed, 0, seed.length); + + MGF1BytesGenerator maskGen = new MGF1BytesGenerator(dig); + + maskGen.init(new MGFParameters(seed)); + + maskGen.generateBytes(mask, 0, mask.length); + + for (int i = 0; i != mask.length; i++) + { + paddedData[i + seed.length] ^= mask[i]; + } + + int end = 0; + + for (int i = paddedData.length - 1; i != seed.length; i--) + { + if (paddedData[i] == 0) + { + end = i; + break; + } + } + + if (end == 0) + { + throw new IllegalStateException("bad padding in encoding"); + } + + byte[] data = new byte[end - seed.length]; + + System.arraycopy(paddedData, seed.length, data, 0, data.length); + + return data; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/PKIArchiveControl.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/PKIArchiveControl.java new file mode 100644 index 000000000..0c9bd985a --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/PKIArchiveControl.java @@ -0,0 +1,104 @@ +package org.spongycastle.cert.crmf; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.cms.CMSObjectIdentifiers; +import org.spongycastle.asn1.cms.ContentInfo; +import org.spongycastle.asn1.cms.EnvelopedData; +import org.spongycastle.asn1.crmf.CRMFObjectIdentifiers; +import org.spongycastle.asn1.crmf.EncryptedKey; +import org.spongycastle.asn1.crmf.PKIArchiveOptions; +import org.spongycastle.cms.CMSEnvelopedData; +import org.spongycastle.cms.CMSException; + +/** + * Carrier for a PKIArchiveOptions structure. + */ +public class PKIArchiveControl + implements Control +{ + public static final int encryptedPrivKey = PKIArchiveOptions.encryptedPrivKey; + public static final int keyGenParameters = PKIArchiveOptions.keyGenParameters; + public static final int archiveRemGenPrivKey = PKIArchiveOptions.archiveRemGenPrivKey; + + private static final ASN1ObjectIdentifier type = CRMFObjectIdentifiers.id_regCtrl_pkiArchiveOptions; + + private final PKIArchiveOptions pkiArchiveOptions; + + /** + * Basic constructor - build from an PKIArchiveOptions structure. + * + * @param pkiArchiveOptions the ASN.1 structure that will underlie this control. + */ + public PKIArchiveControl(PKIArchiveOptions pkiArchiveOptions) + { + this.pkiArchiveOptions = pkiArchiveOptions; + } + + /** + * Return the type of this control. + * + * @return CRMFObjectIdentifiers.id_regCtrl_pkiArchiveOptions + */ + public ASN1ObjectIdentifier getType() + { + return type; + } + + /** + * Return the underlying ASN.1 object. + * + * @return a PKIArchiveOptions structure. + */ + public ASN1Encodable getValue() + { + return pkiArchiveOptions; + } + + /** + * Return the archive control type, one of: encryptedPrivKey,keyGenParameters,or archiveRemGenPrivKey. + * + * @return the archive control type. + */ + public int getArchiveType() + { + return pkiArchiveOptions.getType(); + } + + /** + * Return whether this control contains enveloped data. + * + * @return true if the control contains enveloped data, false otherwise. + */ + public boolean isEnvelopedData() + { + EncryptedKey encKey = EncryptedKey.getInstance(pkiArchiveOptions.getValue()); + + return !encKey.isEncryptedValue(); + } + + /** + * Return the enveloped data structure contained in this control. + * + * @return a CMSEnvelopedData object. + */ + public CMSEnvelopedData getEnvelopedData() + throws CRMFException + { + try + { + EncryptedKey encKey = EncryptedKey.getInstance(pkiArchiveOptions.getValue()); + EnvelopedData data = EnvelopedData.getInstance(encKey.getValue()); + + return new CMSEnvelopedData(new ContentInfo(CMSObjectIdentifiers.envelopedData, data)); + } + catch (CMSException e) + { + throw new CRMFException("CMS parsing error: " + e.getMessage(), e.getCause()); + } + catch (Exception e) + { + throw new CRMFException("CRMF parsing error: " + e.getMessage(), e); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/PKIArchiveControlBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/PKIArchiveControlBuilder.java new file mode 100644 index 000000000..1ca860cb1 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/PKIArchiveControlBuilder.java @@ -0,0 +1,78 @@ +package org.spongycastle.cert.crmf; + +import java.io.IOException; + +import org.spongycastle.asn1.cms.EnvelopedData; +import org.spongycastle.asn1.crmf.CRMFObjectIdentifiers; +import org.spongycastle.asn1.crmf.EncKeyWithID; +import org.spongycastle.asn1.crmf.EncryptedKey; +import org.spongycastle.asn1.crmf.PKIArchiveOptions; +import org.spongycastle.asn1.pkcs.PrivateKeyInfo; +import org.spongycastle.asn1.x509.GeneralName; +import org.spongycastle.cms.CMSEnvelopedData; +import org.spongycastle.cms.CMSEnvelopedDataGenerator; +import org.spongycastle.cms.CMSException; +import org.spongycastle.cms.CMSProcessableByteArray; +import org.spongycastle.cms.RecipientInfoGenerator; +import org.spongycastle.operator.OutputEncryptor; + +/** + * Builder for a PKIArchiveControl structure. + */ +public class PKIArchiveControlBuilder +{ + private CMSEnvelopedDataGenerator envGen; + private CMSProcessableByteArray keyContent; + + /** + * Basic constructor - specify the contents of the PKIArchiveControl structure. + * + * @param privateKeyInfo the private key to be archived. + * @param generalName the general name to be associated with the private key. + */ + public PKIArchiveControlBuilder(PrivateKeyInfo privateKeyInfo, GeneralName generalName) + { + EncKeyWithID encKeyWithID = new EncKeyWithID(privateKeyInfo, generalName); + + try + { + this.keyContent = new CMSProcessableByteArray(CRMFObjectIdentifiers.id_ct_encKeyWithID, encKeyWithID.getEncoded()); + } + catch (IOException e) + { + throw new IllegalStateException("unable to encode key and general name info"); + } + + this.envGen = new CMSEnvelopedDataGenerator(); + } + + /** + * Add a recipient generator to this control. + * + * @param recipientGen recipient generator created for a specific recipient. + * @return this builder object. + */ + public PKIArchiveControlBuilder addRecipientGenerator(RecipientInfoGenerator recipientGen) + { + envGen.addRecipientInfoGenerator(recipientGen); + + return this; + } + + /** + * Build the PKIArchiveControl using the passed in encryptor to encrypt its contents. + * + * @param contentEncryptor a suitable content encryptor. + * @return a PKIArchiveControl object. + * @throws CMSException in the event the build fails. + */ + public PKIArchiveControl build(OutputEncryptor contentEncryptor) + throws CMSException + { + CMSEnvelopedData envContent = envGen.generate(keyContent, contentEncryptor); + + EnvelopedData envD = EnvelopedData.getInstance(envContent.toASN1Structure().getContent()); + + return new PKIArchiveControl(new PKIArchiveOptions(new EncryptedKey(envD))); + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/PKMACBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/PKMACBuilder.java new file mode 100644 index 000000000..54e9cd914 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/PKMACBuilder.java @@ -0,0 +1,199 @@ +package org.spongycastle.cert.crmf; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.security.SecureRandom; + +import org.spongycastle.asn1.DERNull; +import org.spongycastle.asn1.cmp.CMPObjectIdentifiers; +import org.spongycastle.asn1.cmp.PBMParameter; +import org.spongycastle.asn1.iana.IANAObjectIdentifiers; +import org.spongycastle.asn1.oiw.OIWObjectIdentifiers; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.operator.GenericKey; +import org.spongycastle.operator.MacCalculator; +import org.spongycastle.operator.RuntimeOperatorException; +import org.spongycastle.util.Strings; + +public class PKMACBuilder +{ + private AlgorithmIdentifier owf; + private int iterationCount; + private AlgorithmIdentifier mac; + private int saltLength = 20; + private SecureRandom random; + private PKMACValuesCalculator calculator; + private PBMParameter parameters; + private int maxIterations; + + public PKMACBuilder(PKMACValuesCalculator calculator) + { + this(new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1), 1000, new AlgorithmIdentifier(IANAObjectIdentifiers.hmacSHA1, DERNull.INSTANCE), calculator); + } + + /** + * Create a PKMAC builder enforcing a ceiling on the maximum iteration count. + * + * @param calculator supporting calculator + * @param maxIterations max allowable value for iteration count. + */ + public PKMACBuilder(PKMACValuesCalculator calculator, int maxIterations) + { + this.maxIterations = maxIterations; + this.calculator = calculator; + } + + private PKMACBuilder(AlgorithmIdentifier hashAlgorithm, int iterationCount, AlgorithmIdentifier macAlgorithm, PKMACValuesCalculator calculator) + { + this.owf = hashAlgorithm; + this.iterationCount = iterationCount; + this.mac = macAlgorithm; + this.calculator = calculator; + } + + /** + * Set the salt length in octets. + * + * @param saltLength length in octets of the salt to be generated. + * @return the generator + */ + public PKMACBuilder setSaltLength(int saltLength) + { + if (saltLength < 8) + { + throw new IllegalArgumentException("salt length must be at least 8 bytes"); + } + + this.saltLength = saltLength; + + return this; + } + + public PKMACBuilder setIterationCount(int iterationCount) + { + if (iterationCount < 100) + { + throw new IllegalArgumentException("iteration count must be at least 100"); + } + checkIterationCountCeiling(iterationCount); + + this.iterationCount = iterationCount; + + return this; + } + + public PKMACBuilder setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + public PKMACBuilder setParameters(PBMParameter parameters) + { + checkIterationCountCeiling(parameters.getIterationCount().getValue().intValue()); + + this.parameters = parameters; + + return this; + } + + public MacCalculator build(char[] password) + throws CRMFException + { + if (parameters != null) + { + return genCalculator(parameters, password); + } + else + { + byte[] salt = new byte[saltLength]; + + if (random == null) + { + this.random = new SecureRandom(); + } + + random.nextBytes(salt); + + return genCalculator(new PBMParameter(salt, owf, iterationCount, mac), password); + } + } + + private void checkIterationCountCeiling(int iterationCount) + { + if (maxIterations > 0 && iterationCount > maxIterations) + { + throw new IllegalArgumentException("iteration count exceeds limit (" + iterationCount + " > " + maxIterations + ")"); + } + } + + private MacCalculator genCalculator(final PBMParameter params, char[] password) + throws CRMFException + { + // From RFC 4211 + // + // 1. Generate a random salt value S + // + // 2. Append the salt to the pw. K = pw || salt. + // + // 3. Hash the value of K. K = HASH(K) + // + // 4. Iter = Iter - 1. If Iter is greater than zero. Goto step 3. + // + // 5. Compute an HMAC as documented in [HMAC]. + // + // MAC = HASH( K XOR opad, HASH( K XOR ipad, data) ) + // + // Where opad and ipad are defined in [HMAC]. + byte[] pw = Strings.toUTF8ByteArray(password); + byte[] salt = params.getSalt().getOctets(); + byte[] K = new byte[pw.length + salt.length]; + + System.arraycopy(pw, 0, K, 0, pw.length); + System.arraycopy(salt, 0, K, pw.length, salt.length); + + calculator.setup(params.getOwf(), params.getMac()); + + int iter = params.getIterationCount().getValue().intValue(); + do + { + K = calculator.calculateDigest(K); + } + while (--iter > 0); + + final byte[] key = K; + + return new MacCalculator() + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return new AlgorithmIdentifier(CMPObjectIdentifiers.passwordBasedMac, params); + } + + public GenericKey getKey() + { + return new GenericKey(getAlgorithmIdentifier(), key); + } + + public OutputStream getOutputStream() + { + return bOut; + } + + public byte[] getMac() + { + try + { + return calculator.calculateMac(key, bOut.toByteArray()); + } + catch (CRMFException e) + { + throw new RuntimeOperatorException("exception calculating mac: " + e.getMessage(), e); + } + } + }; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/PKMACValueGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/PKMACValueGenerator.java new file mode 100644 index 000000000..eaf215ff7 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/PKMACValueGenerator.java @@ -0,0 +1,41 @@ +package org.spongycastle.cert.crmf; + +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.asn1.ASN1Encoding; +import org.spongycastle.asn1.DERBitString; +import org.spongycastle.asn1.crmf.PKMACValue; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.operator.MacCalculator; + +class PKMACValueGenerator +{ + private PKMACBuilder builder; + + public PKMACValueGenerator(PKMACBuilder builder) + { + this.builder = builder; + } + + public PKMACValue generate(char[] password, SubjectPublicKeyInfo keyInfo) + throws CRMFException + { + MacCalculator calculator = builder.build(password); + + OutputStream macOut = calculator.getOutputStream(); + + try + { + macOut.write(keyInfo.getEncoded(ASN1Encoding.DER)); + + macOut.close(); + } + catch (IOException e) + { + throw new CRMFException("exception encoding mac input: " + e.getMessage(), e); + } + + return new PKMACValue(calculator.getAlgorithmIdentifier(), new DERBitString(calculator.getMac())); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/PKMACValueVerifier.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/PKMACValueVerifier.java new file mode 100644 index 000000000..a65ff61d7 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/PKMACValueVerifier.java @@ -0,0 +1,43 @@ +package org.spongycastle.cert.crmf; + +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.asn1.ASN1Encoding; +import org.spongycastle.asn1.cmp.PBMParameter; +import org.spongycastle.asn1.crmf.PKMACValue; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.operator.MacCalculator; +import org.spongycastle.util.Arrays; + +class PKMACValueVerifier +{ + private final PKMACBuilder builder; + + public PKMACValueVerifier(PKMACBuilder builder) + { + this.builder = builder; + } + + public boolean isValid(PKMACValue value, char[] password, SubjectPublicKeyInfo keyInfo) + throws CRMFException + { + builder.setParameters(PBMParameter.getInstance(value.getAlgId().getParameters())); + MacCalculator calculator = builder.build(password); + + OutputStream macOut = calculator.getOutputStream(); + + try + { + macOut.write(keyInfo.getEncoded(ASN1Encoding.DER)); + + macOut.close(); + } + catch (IOException e) + { + throw new CRMFException("exception encoding mac input: " + e.getMessage(), e); + } + + return Arrays.areEqual(calculator.getMac(), value.getValue().getBytes()); + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/PKMACValuesCalculator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/PKMACValuesCalculator.java new file mode 100644 index 000000000..0b4f14077 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/PKMACValuesCalculator.java @@ -0,0 +1,15 @@ +package org.spongycastle.cert.crmf; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +public interface PKMACValuesCalculator +{ + void setup(AlgorithmIdentifier digestAlg, AlgorithmIdentifier macAlg) + throws CRMFException; + + byte[] calculateDigest(byte[] data) + throws CRMFException; + + byte[] calculateMac(byte[] pwd, byte[] data) + throws CRMFException; +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/ProofOfPossessionSigningKeyBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/ProofOfPossessionSigningKeyBuilder.java new file mode 100644 index 000000000..07659593c --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/ProofOfPossessionSigningKeyBuilder.java @@ -0,0 +1,75 @@ +package org.spongycastle.cert.crmf; + +import org.spongycastle.asn1.DERBitString; +import org.spongycastle.asn1.crmf.CertRequest; +import org.spongycastle.asn1.crmf.PKMACValue; +import org.spongycastle.asn1.crmf.POPOSigningKey; +import org.spongycastle.asn1.crmf.POPOSigningKeyInput; +import org.spongycastle.asn1.x509.GeneralName; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.operator.ContentSigner; + +public class ProofOfPossessionSigningKeyBuilder +{ + private CertRequest certRequest; + private SubjectPublicKeyInfo pubKeyInfo; + private GeneralName name; + private PKMACValue publicKeyMAC; + + public ProofOfPossessionSigningKeyBuilder(CertRequest certRequest) + { + this.certRequest = certRequest; + } + + + public ProofOfPossessionSigningKeyBuilder(SubjectPublicKeyInfo pubKeyInfo) + { + this.pubKeyInfo = pubKeyInfo; + } + + public ProofOfPossessionSigningKeyBuilder setSender(GeneralName name) + { + this.name = name; + + return this; + } + + public ProofOfPossessionSigningKeyBuilder setPublicKeyMac(PKMACValueGenerator generator, char[] password) + throws CRMFException + { + this.publicKeyMAC = generator.generate(password, pubKeyInfo); + + return this; + } + + public POPOSigningKey build(ContentSigner signer) + { + if (name != null && publicKeyMAC != null) + { + throw new IllegalStateException("name and publicKeyMAC cannot both be set."); + } + + POPOSigningKeyInput popo; + + if (certRequest != null) + { + popo = null; + + CRMFUtil.derEncodeToStream(certRequest, signer.getOutputStream()); + } + else if (name != null) + { + popo = new POPOSigningKeyInput(name, pubKeyInfo); + + CRMFUtil.derEncodeToStream(popo, signer.getOutputStream()); + } + else + { + popo = new POPOSigningKeyInput(publicKeyMAC, pubKeyInfo); + + CRMFUtil.derEncodeToStream(popo, signer.getOutputStream()); + } + + return new POPOSigningKey(popo, signer.getAlgorithmIdentifier(), new DERBitString(signer.getSignature())); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/RegTokenControl.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/RegTokenControl.java new file mode 100644 index 000000000..b1a6eb7ec --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/RegTokenControl.java @@ -0,0 +1,57 @@ +package org.spongycastle.cert.crmf; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.DERUTF8String; +import org.spongycastle.asn1.crmf.CRMFObjectIdentifiers; + +/** + * Carrier for a registration token control. + */ +public class RegTokenControl + implements Control +{ + private static final ASN1ObjectIdentifier type = CRMFObjectIdentifiers.id_regCtrl_regToken; + + private final DERUTF8String token; + + /** + * Basic constructor - build from a UTF-8 string representing the token. + * + * @param token UTF-8 string representing the token. + */ + public RegTokenControl(DERUTF8String token) + { + this.token = token; + } + + /** + * Basic constructor - build from a string representing the token. + * + * @param token string representing the token. + */ + public RegTokenControl(String token) + { + this.token = new DERUTF8String(token); + } + + /** + * Return the type of this control. + * + * @return CRMFObjectIdentifiers.id_regCtrl_regToken + */ + public ASN1ObjectIdentifier getType() + { + return type; + } + + /** + * Return the token associated with this control (a UTF8String). + * + * @return a UTF8String. + */ + public ASN1Encodable getValue() + { + return token; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/ValueDecryptorGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/ValueDecryptorGenerator.java new file mode 100644 index 000000000..3fcee4ebd --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/ValueDecryptorGenerator.java @@ -0,0 +1,10 @@ +package org.spongycastle.cert.crmf; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.operator.InputDecryptor; + +public interface ValueDecryptorGenerator +{ + InputDecryptor getValueDecryptor(AlgorithmIdentifier keyAlg, AlgorithmIdentifier symmAlg, byte[] encKey) + throws CRMFException; +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/CRMFHelper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/CRMFHelper.java new file mode 100644 index 000000000..212ac914d --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/CRMFHelper.java @@ -0,0 +1,450 @@ +package org.spongycastle.cert.crmf.jcajce; + +import java.io.IOException; +import java.security.AlgorithmParameterGenerator; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyFactory; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.spec.InvalidParameterSpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.Mac; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.RC2ParameterSpec; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1Null; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.DERNull; +import org.spongycastle.asn1.iana.IANAObjectIdentifiers; +import org.spongycastle.asn1.nist.NISTObjectIdentifiers; +import org.spongycastle.asn1.oiw.OIWObjectIdentifiers; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.asn1.x9.X9ObjectIdentifiers; +import org.spongycastle.cert.crmf.CRMFException; +import org.spongycastle.cms.CMSAlgorithm; +import org.spongycastle.jcajce.JcaJceHelper; +import org.spongycastle.jcajce.JcaJceUtils; + +class CRMFHelper +{ + protected static final Map BASE_CIPHER_NAMES = new HashMap(); + protected static final Map CIPHER_ALG_NAMES = new HashMap(); + protected static final Map DIGEST_ALG_NAMES = new HashMap(); + protected static final Map KEY_ALG_NAMES = new HashMap(); + protected static final Map MAC_ALG_NAMES = new HashMap(); + + static + { + BASE_CIPHER_NAMES.put(PKCSObjectIdentifiers.des_EDE3_CBC, "DESEDE"); + BASE_CIPHER_NAMES.put(NISTObjectIdentifiers.id_aes128_CBC, "AES"); + BASE_CIPHER_NAMES.put(NISTObjectIdentifiers.id_aes192_CBC, "AES"); + BASE_CIPHER_NAMES.put(NISTObjectIdentifiers.id_aes256_CBC, "AES"); + + CIPHER_ALG_NAMES.put(CMSAlgorithm.DES_EDE3_CBC, "DESEDE/CBC/PKCS5Padding"); + CIPHER_ALG_NAMES.put(CMSAlgorithm.AES128_CBC, "AES/CBC/PKCS5Padding"); + CIPHER_ALG_NAMES.put(CMSAlgorithm.AES192_CBC, "AES/CBC/PKCS5Padding"); + CIPHER_ALG_NAMES.put(CMSAlgorithm.AES256_CBC, "AES/CBC/PKCS5Padding"); + CIPHER_ALG_NAMES.put(new ASN1ObjectIdentifier(PKCSObjectIdentifiers.rsaEncryption.getId()), "RSA/ECB/PKCS1Padding"); + + DIGEST_ALG_NAMES.put(OIWObjectIdentifiers.idSHA1, "SHA1"); + DIGEST_ALG_NAMES.put(NISTObjectIdentifiers.id_sha224, "SHA224"); + DIGEST_ALG_NAMES.put(NISTObjectIdentifiers.id_sha256, "SHA256"); + DIGEST_ALG_NAMES.put(NISTObjectIdentifiers.id_sha384, "SHA384"); + DIGEST_ALG_NAMES.put(NISTObjectIdentifiers.id_sha512, "SHA512"); + + MAC_ALG_NAMES.put(IANAObjectIdentifiers.hmacSHA1, "HMACSHA1"); + MAC_ALG_NAMES.put(PKCSObjectIdentifiers.id_hmacWithSHA1, "HMACSHA1"); + MAC_ALG_NAMES.put(PKCSObjectIdentifiers.id_hmacWithSHA224, "HMACSHA224"); + MAC_ALG_NAMES.put(PKCSObjectIdentifiers.id_hmacWithSHA256, "HMACSHA256"); + MAC_ALG_NAMES.put(PKCSObjectIdentifiers.id_hmacWithSHA384, "HMACSHA384"); + MAC_ALG_NAMES.put(PKCSObjectIdentifiers.id_hmacWithSHA512, "HMACSHA512"); + + KEY_ALG_NAMES.put(PKCSObjectIdentifiers.rsaEncryption, "RSA"); + KEY_ALG_NAMES.put(X9ObjectIdentifiers.id_dsa, "DSA"); + } + + private JcaJceHelper helper; + + CRMFHelper(JcaJceHelper helper) + { + this.helper = helper; + } + + PublicKey toPublicKey(SubjectPublicKeyInfo subjectPublicKeyInfo) + throws CRMFException + { + try + { + X509EncodedKeySpec xspec = new X509EncodedKeySpec(subjectPublicKeyInfo.getEncoded()); + AlgorithmIdentifier keyAlg = subjectPublicKeyInfo.getAlgorithm(); + + return createKeyFactory(keyAlg.getAlgorithm()).generatePublic(xspec); + } + catch (Exception e) + { + throw new CRMFException("invalid key: " + e.getMessage(), e); + } + } + + Cipher createCipher(ASN1ObjectIdentifier algorithm) + throws CRMFException + { + try + { + String cipherName = (String)CIPHER_ALG_NAMES.get(algorithm); + + if (cipherName != null) + { + try + { + // this is reversed as the Sun policy files now allow unlimited strength RSA + return helper.createCipher(cipherName); + } + catch (NoSuchAlgorithmException e) + { + // Ignore + } + } + return helper.createCipher(algorithm.getId()); + } + catch (GeneralSecurityException e) + { + throw new CRMFException("cannot create cipher: " + e.getMessage(), e); + } + } + + public KeyGenerator createKeyGenerator(ASN1ObjectIdentifier algorithm) + throws CRMFException + { + try + { + String cipherName = (String)BASE_CIPHER_NAMES.get(algorithm); + + if (cipherName != null) + { + try + { + // this is reversed as the Sun policy files now allow unlimited strength RSA + return helper.createKeyGenerator(cipherName); + } + catch (NoSuchAlgorithmException e) + { + // Ignore + } + } + return helper.createKeyGenerator(algorithm.getId()); + } + catch (GeneralSecurityException e) + { + throw new CRMFException("cannot create key generator: " + e.getMessage(), e); + } + } + + + + Cipher createContentCipher(final Key sKey, final AlgorithmIdentifier encryptionAlgID) + throws CRMFException + { + return (Cipher)execute(new JCECallback() + { + public Object doInJCE() + throws CRMFException, InvalidAlgorithmParameterException, + InvalidKeyException, InvalidParameterSpecException, NoSuchAlgorithmException, + NoSuchPaddingException, NoSuchProviderException + { + Cipher cipher = createCipher(encryptionAlgID.getAlgorithm()); + ASN1Primitive sParams = (ASN1Primitive)encryptionAlgID.getParameters(); + ASN1ObjectIdentifier encAlg = encryptionAlgID.getAlgorithm(); + + if (sParams != null && !(sParams instanceof ASN1Null)) + { + try + { + AlgorithmParameters params = createAlgorithmParameters(encryptionAlgID.getAlgorithm()); + + try + { + JcaJceUtils.loadParameters(params, sParams); + } + catch (IOException e) + { + throw new CRMFException("error decoding algorithm parameters.", e); + } + + cipher.init(Cipher.DECRYPT_MODE, sKey, params); + } + catch (NoSuchAlgorithmException e) + { + if (encAlg.equals(CMSAlgorithm.DES_EDE3_CBC) + || encAlg.equals(CMSAlgorithm.IDEA_CBC) + || encAlg.equals(CMSAlgorithm.AES128_CBC) + || encAlg.equals(CMSAlgorithm.AES192_CBC) + || encAlg.equals(CMSAlgorithm.AES256_CBC)) + { + cipher.init(Cipher.DECRYPT_MODE, sKey, new IvParameterSpec( + ASN1OctetString.getInstance(sParams).getOctets())); + } + else + { + throw e; + } + } + } + else + { + if (encAlg.equals(CMSAlgorithm.DES_EDE3_CBC) + || encAlg.equals(CMSAlgorithm.IDEA_CBC) + || encAlg.equals(CMSAlgorithm.CAST5_CBC)) + { + cipher.init(Cipher.DECRYPT_MODE, sKey, new IvParameterSpec(new byte[8])); + } + else + { + cipher.init(Cipher.DECRYPT_MODE, sKey); + } + } + + return cipher; + } + }); + } + + AlgorithmParameters createAlgorithmParameters(ASN1ObjectIdentifier algorithm) + throws NoSuchAlgorithmException, NoSuchProviderException + { + String algorithmName = (String)BASE_CIPHER_NAMES.get(algorithm); + + if (algorithmName != null) + { + try + { + // this is reversed as the Sun policy files now allow unlimited strength RSA + return helper.createAlgorithmParameters(algorithmName); + } + catch (NoSuchAlgorithmException e) + { + // Ignore + } + } + return helper.createAlgorithmParameters(algorithm.getId()); + } + + KeyFactory createKeyFactory(ASN1ObjectIdentifier algorithm) + throws CRMFException + { + try + { + String algName = (String)KEY_ALG_NAMES.get(algorithm); + + if (algName != null) + { + try + { + // this is reversed as the Sun policy files now allow unlimited strength RSA + return helper.createKeyFactory(algName); + } + catch (NoSuchAlgorithmException e) + { + // Ignore + } + } + return helper.createKeyFactory(algorithm.getId()); + } + catch (GeneralSecurityException e) + { + throw new CRMFException("cannot create cipher: " + e.getMessage(), e); + } + } + + MessageDigest createDigest(ASN1ObjectIdentifier algorithm) + throws CRMFException + { + try + { + String digestName = (String)DIGEST_ALG_NAMES.get(algorithm); + + if (digestName != null) + { + try + { + // this is reversed as the Sun policy files now allow unlimited strength RSA + return helper.createDigest(digestName); + } + catch (NoSuchAlgorithmException e) + { + // Ignore + } + } + return helper.createDigest(algorithm.getId()); + } + catch (GeneralSecurityException e) + { + throw new CRMFException("cannot create cipher: " + e.getMessage(), e); + } + } + + Mac createMac(ASN1ObjectIdentifier algorithm) + throws CRMFException + { + try + { + String macName = (String)MAC_ALG_NAMES.get(algorithm); + + if (macName != null) + { + try + { + // this is reversed as the Sun policy files now allow unlimited strength RSA + return helper.createMac(macName); + } + catch (NoSuchAlgorithmException e) + { + // Ignore + } + } + return helper.createMac(algorithm.getId()); + } + catch (GeneralSecurityException e) + { + throw new CRMFException("cannot create mac: " + e.getMessage(), e); + } + } + + AlgorithmParameterGenerator createAlgorithmParameterGenerator(ASN1ObjectIdentifier algorithm) + throws GeneralSecurityException + { + String algorithmName = (String)BASE_CIPHER_NAMES.get(algorithm); + + if (algorithmName != null) + { + try + { + // this is reversed as the Sun policy files now allow unlimited strength RSA + return helper.createAlgorithmParameterGenerator(algorithmName); + } + catch (NoSuchAlgorithmException e) + { + // Ignore + } + } + return helper.createAlgorithmParameterGenerator(algorithm.getId()); + } + + AlgorithmParameters generateParameters(ASN1ObjectIdentifier encryptionOID, SecretKey encKey, SecureRandom rand) + throws CRMFException + { + try + { + AlgorithmParameterGenerator pGen = createAlgorithmParameterGenerator(encryptionOID); + + if (encryptionOID.equals(CMSAlgorithm.RC2_CBC)) + { + byte[] iv = new byte[8]; + + rand.nextBytes(iv); + + try + { + pGen.init(new RC2ParameterSpec(encKey.getEncoded().length * 8, iv), rand); + } + catch (InvalidAlgorithmParameterException e) + { + throw new CRMFException("parameters generation error: " + e, e); + } + } + + return pGen.generateParameters(); + } + catch (NoSuchAlgorithmException e) + { + return null; + } + catch (GeneralSecurityException e) + { + throw new CRMFException("exception creating algorithm parameter generator: " + e, e); + } + } + + AlgorithmIdentifier getAlgorithmIdentifier(ASN1ObjectIdentifier encryptionOID, AlgorithmParameters params) + throws CRMFException + { + ASN1Encodable asn1Params; + if (params != null) + { + try + { + asn1Params = JcaJceUtils.extractParameters(params); + } + catch (IOException e) + { + throw new CRMFException("cannot encode parameters: " + e.getMessage(), e); + } + } + else + { + asn1Params = DERNull.INSTANCE; + } + + return new AlgorithmIdentifier( + encryptionOID, + asn1Params); + } + + static Object execute(JCECallback callback) throws CRMFException + { + try + { + return callback.doInJCE(); + } + catch (NoSuchAlgorithmException e) + { + throw new CRMFException("can't find algorithm.", e); + } + catch (InvalidKeyException e) + { + throw new CRMFException("key invalid in message.", e); + } + catch (NoSuchProviderException e) + { + throw new CRMFException("can't find provider.", e); + } + catch (NoSuchPaddingException e) + { + throw new CRMFException("required padding not supported.", e); + } + catch (InvalidAlgorithmParameterException e) + { + throw new CRMFException("algorithm parameters invalid.", e); + } + catch (InvalidParameterSpecException e) + { + throw new CRMFException("MAC algorithm parameter spec invalid.", e); + } + } + + static interface JCECallback + { + Object doInJCE() + throws CRMFException, InvalidAlgorithmParameterException, InvalidKeyException, InvalidParameterSpecException, + NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JcaCertificateRequestMessage.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JcaCertificateRequestMessage.java new file mode 100644 index 000000000..1aabc3b95 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JcaCertificateRequestMessage.java @@ -0,0 +1,84 @@ +package org.spongycastle.cert.crmf.jcajce; + +import java.io.IOException; +import java.security.Provider; +import java.security.PublicKey; + +import javax.security.auth.x500.X500Principal; + +import org.spongycastle.asn1.ASN1Encoding; +import org.spongycastle.asn1.crmf.CertReqMsg; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.cert.crmf.CRMFException; +import org.spongycastle.cert.crmf.CertificateRequestMessage; +import org.spongycastle.jcajce.DefaultJcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.ProviderJcaJceHelper; + +public class JcaCertificateRequestMessage + extends CertificateRequestMessage +{ + private CRMFHelper helper = new CRMFHelper(new DefaultJcaJceHelper()); + + public JcaCertificateRequestMessage(byte[] certReqMsg) + { + this(CertReqMsg.getInstance(certReqMsg)); + } + + public JcaCertificateRequestMessage(CertificateRequestMessage certReqMsg) + { + this(certReqMsg.toASN1Structure()); + } + + public JcaCertificateRequestMessage(CertReqMsg certReqMsg) + { + super(certReqMsg); + } + + public JcaCertificateRequestMessage setProvider(String providerName) + { + this.helper = new CRMFHelper(new NamedJcaJceHelper(providerName)); + + return this; + } + + public JcaCertificateRequestMessage setProvider(Provider provider) + { + this.helper = new CRMFHelper(new ProviderJcaJceHelper(provider)); + + return this; + } + + public X500Principal getSubjectX500Principal() + { + X500Name subject = this.getCertTemplate().getSubject(); + + if (subject != null) + { + try + { + return new X500Principal(subject.getEncoded(ASN1Encoding.DER)); + } + catch (IOException e) + { + throw new IllegalStateException("unable to construct DER encoding of name: " + e.getMessage()); + } + } + + return null; + } + + public PublicKey getPublicKey() + throws CRMFException + { + SubjectPublicKeyInfo subjectPublicKeyInfo = getCertTemplate().getPublicKey(); + + if (subjectPublicKeyInfo != null) + { + return helper.toPublicKey(subjectPublicKeyInfo); + } + + return null; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JcaCertificateRequestMessageBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JcaCertificateRequestMessageBuilder.java new file mode 100644 index 000000000..1d9318ddc --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JcaCertificateRequestMessageBuilder.java @@ -0,0 +1,57 @@ +package org.spongycastle.cert.crmf.jcajce; + +import java.math.BigInteger; +import java.security.PublicKey; + +import javax.security.auth.x500.X500Principal; + +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x509.GeneralName; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.cert.crmf.CertificateRequestMessageBuilder; + +public class JcaCertificateRequestMessageBuilder + extends CertificateRequestMessageBuilder +{ + public JcaCertificateRequestMessageBuilder(BigInteger certReqId) + { + super(certReqId); + } + + public JcaCertificateRequestMessageBuilder setIssuer(X500Principal issuer) + { + if (issuer != null) + { + setIssuer(X500Name.getInstance(issuer.getEncoded())); + } + + return this; + } + + public JcaCertificateRequestMessageBuilder setSubject(X500Principal subject) + { + if (subject != null) + { + setSubject(X500Name.getInstance(subject.getEncoded())); + } + + return this; + } + + public JcaCertificateRequestMessageBuilder setAuthInfoSender(X500Principal sender) + { + if (sender != null) + { + setAuthInfoSender(new GeneralName(X500Name.getInstance(sender.getEncoded()))); + } + + return this; + } + + public JcaCertificateRequestMessageBuilder setPublicKey(PublicKey publicKey) + { + setPublicKey(SubjectPublicKeyInfo.getInstance(publicKey.getEncoded())); + + return this; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JcaEncryptedValueBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JcaEncryptedValueBuilder.java new file mode 100644 index 000000000..bed393dc4 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JcaEncryptedValueBuilder.java @@ -0,0 +1,26 @@ +package org.spongycastle.cert.crmf.jcajce; + +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; + +import org.spongycastle.asn1.crmf.EncryptedValue; +import org.spongycastle.cert.crmf.CRMFException; +import org.spongycastle.cert.crmf.EncryptedValueBuilder; +import org.spongycastle.cert.jcajce.JcaX509CertificateHolder; +import org.spongycastle.operator.KeyWrapper; +import org.spongycastle.operator.OutputEncryptor; + +public class JcaEncryptedValueBuilder + extends EncryptedValueBuilder +{ + public JcaEncryptedValueBuilder(KeyWrapper wrapper, OutputEncryptor encryptor) + { + super(wrapper, encryptor); + } + + public EncryptedValue build(X509Certificate certificate) + throws CertificateEncodingException, CRMFException + { + return build(new JcaX509CertificateHolder(certificate)); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JcaPKIArchiveControlBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JcaPKIArchiveControlBuilder.java new file mode 100644 index 000000000..de527e07d --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JcaPKIArchiveControlBuilder.java @@ -0,0 +1,29 @@ +package org.spongycastle.cert.crmf.jcajce; + +import java.security.PrivateKey; + +import javax.security.auth.x500.X500Principal; + +import org.spongycastle.asn1.pkcs.PrivateKeyInfo; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x509.GeneralName; +import org.spongycastle.cert.crmf.PKIArchiveControlBuilder; + +public class JcaPKIArchiveControlBuilder + extends PKIArchiveControlBuilder +{ + public JcaPKIArchiveControlBuilder(PrivateKey privateKey, X500Name name) + { + this(privateKey, new GeneralName(name)); + } + + public JcaPKIArchiveControlBuilder(PrivateKey privateKey, X500Principal name) + { + this(privateKey, X500Name.getInstance(name.getEncoded())); + } + + public JcaPKIArchiveControlBuilder(PrivateKey privateKey, GeneralName generalName) + { + super(PrivateKeyInfo.getInstance(privateKey.getEncoded()), generalName); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JceAsymmetricValueDecryptorGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JceAsymmetricValueDecryptorGenerator.java new file mode 100644 index 000000000..870c4b3b8 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JceAsymmetricValueDecryptorGenerator.java @@ -0,0 +1,120 @@ +package org.spongycastle.cert.crmf.jcajce; + +import java.io.InputStream; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.ProviderException; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.spec.SecretKeySpec; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cert.crmf.CRMFException; +import org.spongycastle.cert.crmf.ValueDecryptorGenerator; +import org.spongycastle.jcajce.DefaultJcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.ProviderJcaJceHelper; +import org.spongycastle.operator.InputDecryptor; + +public class JceAsymmetricValueDecryptorGenerator + implements ValueDecryptorGenerator +{ + private PrivateKey recipientKey; + private CRMFHelper helper = new CRMFHelper(new DefaultJcaJceHelper()); + + public JceAsymmetricValueDecryptorGenerator(PrivateKey recipientKey) + { + this.recipientKey = recipientKey; + } + + public JceAsymmetricValueDecryptorGenerator setProvider(Provider provider) + { + this.helper = new CRMFHelper(new ProviderJcaJceHelper(provider)); + + return this; + } + + public JceAsymmetricValueDecryptorGenerator setProvider(String providerName) + { + this.helper = new CRMFHelper(new NamedJcaJceHelper(providerName)); + + return this; + } + + private Key extractSecretKey(AlgorithmIdentifier keyEncryptionAlgorithm, AlgorithmIdentifier contentEncryptionAlgorithm, byte[] encryptedContentEncryptionKey) + throws CRMFException + { + try + { + Key sKey = null; + + Cipher keyCipher = helper.createCipher(keyEncryptionAlgorithm.getAlgorithm()); + + try + { + keyCipher.init(Cipher.UNWRAP_MODE, recipientKey); + sKey = keyCipher.unwrap(encryptedContentEncryptionKey, contentEncryptionAlgorithm.getAlgorithm().getId(), Cipher.SECRET_KEY); + } + catch (GeneralSecurityException e) + { + } + catch (IllegalStateException e) + { + } + catch (UnsupportedOperationException e) + { + } + catch (ProviderException e) + { + } + + // some providers do not support UNWRAP (this appears to be only for asymmetric algorithms) + if (sKey == null) + { + keyCipher.init(Cipher.DECRYPT_MODE, recipientKey); + sKey = new SecretKeySpec(keyCipher.doFinal(encryptedContentEncryptionKey), contentEncryptionAlgorithm.getAlgorithm().getId()); + } + + return sKey; + } + catch (InvalidKeyException e) + { + throw new CRMFException("key invalid in message.", e); + } + catch (IllegalBlockSizeException e) + { + throw new CRMFException("illegal blocksize in message.", e); + } + catch (BadPaddingException e) + { + throw new CRMFException("bad padding in message.", e); + } + } + + public InputDecryptor getValueDecryptor(AlgorithmIdentifier keyEncryptionAlgorithm, final AlgorithmIdentifier contentEncryptionAlgorithm, byte[] encryptedContentEncryptionKey) + throws CRMFException + { + Key secretKey = extractSecretKey(keyEncryptionAlgorithm, contentEncryptionAlgorithm, encryptedContentEncryptionKey); + + final Cipher dataCipher = helper.createContentCipher(secretKey, contentEncryptionAlgorithm); + + return new InputDecryptor() + { + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return contentEncryptionAlgorithm; + } + + public InputStream getInputStream(InputStream dataIn) + { + return new CipherInputStream(dataIn, dataCipher); + } + }; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JceCRMFEncryptorBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JceCRMFEncryptorBuilder.java new file mode 100644 index 000000000..8226bb3b6 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JceCRMFEncryptorBuilder.java @@ -0,0 +1,136 @@ +package org.spongycastle.cert.crmf.jcajce; + +import java.io.OutputStream; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.Provider; +import java.security.SecureRandom; + +import javax.crypto.Cipher; +import javax.crypto.CipherOutputStream; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cert.crmf.CRMFException; +import org.spongycastle.jcajce.DefaultJcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.ProviderJcaJceHelper; +import org.spongycastle.operator.GenericKey; +import org.spongycastle.operator.OutputEncryptor; +import org.spongycastle.operator.jcajce.JceGenericKey; + +public class JceCRMFEncryptorBuilder +{ + private final ASN1ObjectIdentifier encryptionOID; + private final int keySize; + + private CRMFHelper helper = new CRMFHelper(new DefaultJcaJceHelper()); + private SecureRandom random; + + public JceCRMFEncryptorBuilder(ASN1ObjectIdentifier encryptionOID) + { + this(encryptionOID, -1); + } + + public JceCRMFEncryptorBuilder(ASN1ObjectIdentifier encryptionOID, int keySize) + { + this.encryptionOID = encryptionOID; + this.keySize = keySize; + } + + public JceCRMFEncryptorBuilder setProvider(Provider provider) + { + this.helper = new CRMFHelper(new ProviderJcaJceHelper(provider)); + + return this; + } + + public JceCRMFEncryptorBuilder setProvider(String providerName) + { + this.helper = new CRMFHelper(new NamedJcaJceHelper(providerName)); + + return this; + } + + public JceCRMFEncryptorBuilder setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + public OutputEncryptor build() + throws CRMFException + { + return new CRMFOutputEncryptor(encryptionOID, keySize, random); + } + + private class CRMFOutputEncryptor + implements OutputEncryptor + { + private SecretKey encKey; + private AlgorithmIdentifier algorithmIdentifier; + private Cipher cipher; + + CRMFOutputEncryptor(ASN1ObjectIdentifier encryptionOID, int keySize, SecureRandom random) + throws CRMFException + { + KeyGenerator keyGen = helper.createKeyGenerator(encryptionOID); + + if (random == null) + { + random = new SecureRandom(); + } + + if (keySize < 0) + { + keyGen.init(random); + } + else + { + keyGen.init(keySize, random); + } + + cipher = helper.createCipher(encryptionOID); + encKey = keyGen.generateKey(); + AlgorithmParameters params = helper.generateParameters(encryptionOID, encKey, random); + + try + { + cipher.init(Cipher.ENCRYPT_MODE, encKey, params, random); + } + catch (GeneralSecurityException e) + { + throw new CRMFException("unable to initialize cipher: " + e.getMessage(), e); + } + + // + // If params are null we try and second guess on them as some providers don't provide + // algorithm parameter generation explicity but instead generate them under the hood. + // + if (params == null) + { + params = cipher.getParameters(); + } + + algorithmIdentifier = helper.getAlgorithmIdentifier(encryptionOID, params); + } + + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return algorithmIdentifier; + } + + public OutputStream getOutputStream(OutputStream dOut) + { + return new CipherOutputStream(dOut, cipher); + } + + public GenericKey getKey() + { + return new JceGenericKey(algorithmIdentifier, encKey); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JcePKMACValuesCalculator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JcePKMACValuesCalculator.java new file mode 100644 index 000000000..c11f9535e --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JcePKMACValuesCalculator.java @@ -0,0 +1,69 @@ +package org.spongycastle.cert.crmf.jcajce; + +import java.security.GeneralSecurityException; +import java.security.MessageDigest; +import java.security.Provider; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cert.crmf.CRMFException; +import org.spongycastle.cert.crmf.PKMACValuesCalculator; +import org.spongycastle.jcajce.DefaultJcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.ProviderJcaJceHelper; + +public class JcePKMACValuesCalculator + implements PKMACValuesCalculator +{ + private MessageDigest digest; + private Mac mac; + private CRMFHelper helper; + + public JcePKMACValuesCalculator() + { + this.helper = new CRMFHelper(new DefaultJcaJceHelper()); + } + + public JcePKMACValuesCalculator setProvider(Provider provider) + { + this.helper = new CRMFHelper(new ProviderJcaJceHelper(provider)); + + return this; + } + + public JcePKMACValuesCalculator setProvider(String providerName) + { + this.helper = new CRMFHelper(new NamedJcaJceHelper(providerName)); + + return this; + } + + public void setup(AlgorithmIdentifier digAlg, AlgorithmIdentifier macAlg) + throws CRMFException + { + digest = helper.createDigest(digAlg.getAlgorithm()); + mac = helper.createMac(macAlg.getAlgorithm()); + } + + public byte[] calculateDigest(byte[] data) + { + return digest.digest(data); + } + + public byte[] calculateMac(byte[] pwd, byte[] data) + throws CRMFException + { + try + { + mac.init(new SecretKeySpec(pwd, mac.getAlgorithm())); + + return mac.doFinal(data); + } + catch (GeneralSecurityException e) + { + throw new CRMFException("failure in setup: " + e.getMessage(), e); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/CertHelper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/CertHelper.java new file mode 100644 index 000000000..1c5679bf3 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/CertHelper.java @@ -0,0 +1,17 @@ +package org.spongycastle.cert.jcajce; + +import java.security.NoSuchProviderException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; + +abstract class CertHelper +{ + public CertificateFactory getCertificateFactory(String type) + throws NoSuchProviderException, CertificateException + { + return createCertificateFactory(type); + } + + protected abstract CertificateFactory createCertificateFactory(String type) + throws CertificateException, NoSuchProviderException; +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/DefaultCertHelper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/DefaultCertHelper.java new file mode 100644 index 000000000..d8713bf38 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/DefaultCertHelper.java @@ -0,0 +1,14 @@ +package org.spongycastle.cert.jcajce; + +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; + +class DefaultCertHelper + extends CertHelper +{ + protected CertificateFactory createCertificateFactory(String type) + throws CertificateException + { + return CertificateFactory.getInstance(type); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaAttrCertStore.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaAttrCertStore.java new file mode 100644 index 000000000..ed3543355 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaAttrCertStore.java @@ -0,0 +1,62 @@ +package org.spongycastle.cert.jcajce; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import org.spongycastle.util.CollectionStore; +import org.spongycastle.x509.X509AttributeCertificate; + +/** + * Class for storing Attribute Certificates for later lookup. + * <p> + * The class will convert X509AttributeCertificate objects into X509AttributeCertificateHolder objects. + * </p> + */ +public class JcaAttrCertStore + extends CollectionStore +{ + /** + * Basic constructor. + * + * @param collection - initial contents for the store, this is copied. + */ + public JcaAttrCertStore(Collection collection) + throws IOException + { + super(convertCerts(collection)); + } + + public JcaAttrCertStore(X509AttributeCertificate attrCert) + throws IOException + { + this(Collections.singletonList(attrCert)); + } + + private static Collection convertCerts(Collection collection) + throws IOException + { + List list = new ArrayList(collection.size()); + + for (Iterator it = collection.iterator(); it.hasNext();) + { + Object o = it.next(); + + if (o instanceof X509AttributeCertificate) + { + X509AttributeCertificate cert = (X509AttributeCertificate)o; + + list.add(new JcaX509AttributeCertificateHolder(cert)); + } + else + { + list.add(o); + } + } + + return list; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaCRLStore.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaCRLStore.java new file mode 100644 index 000000000..08493d138 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaCRLStore.java @@ -0,0 +1,63 @@ +package org.spongycastle.cert.jcajce; + +import java.io.IOException; +import java.security.cert.CRLException; +import java.security.cert.X509CRL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import org.spongycastle.cert.X509CRLHolder; +import org.spongycastle.util.CollectionStore; + +/** + * Class for storing CRLs for later lookup. + * <p> + * The class will convert X509CRL objects into X509CRLHolder objects. + * </p> + */ +public class JcaCRLStore + extends CollectionStore +{ + /** + * Basic constructor. + * + * @param collection - initial contents for the store, this is copied. + */ + public JcaCRLStore(Collection collection) + throws CRLException + { + super(convertCRLs(collection)); + } + + private static Collection convertCRLs(Collection collection) + throws CRLException + { + List list = new ArrayList(collection.size()); + + for (Iterator it = collection.iterator(); it.hasNext();) + { + Object crl = it.next(); + + if (crl instanceof X509CRL) + { + try + { + list.add(new X509CRLHolder(((X509CRL)crl).getEncoded())); + } + catch (IOException e) + { + throw new CRLException("cannot read encoding: " + e.getMessage()); + + } + } + else + { + list.add((X509CRLHolder)crl); + } + } + + return list; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaCertStore.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaCertStore.java new file mode 100644 index 000000000..497668142 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaCertStore.java @@ -0,0 +1,64 @@ +package org.spongycastle.cert.jcajce; + +import java.io.IOException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.util.CollectionStore; + +/** + * Class for storing Certificates for later lookup. + * <p> + * The class will convert X509Certificate objects into X509CertificateHolder objects. + * </p> + */ +public class JcaCertStore + extends CollectionStore +{ + /** + * Basic constructor. + * + * @param collection - initial contents for the store, this is copied. + */ + public JcaCertStore(Collection collection) + throws CertificateEncodingException + { + super(convertCerts(collection)); + } + + private static Collection convertCerts(Collection collection) + throws CertificateEncodingException + { + List list = new ArrayList(collection.size()); + + for (Iterator it = collection.iterator(); it.hasNext();) + { + Object o = it.next(); + + if (o instanceof X509Certificate) + { + X509Certificate cert = (X509Certificate)o; + + try + { + list.add(new X509CertificateHolder(cert.getEncoded())); + } + catch (IOException e) + { + throw new CertificateEncodingException("unable to read encoding: " + e.getMessage()); + } + } + else + { + list.add((X509CertificateHolder)o); + } + } + + return list; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaCertStoreBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaCertStoreBuilder.java new file mode 100644 index 000000000..fbb26cb45 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaCertStoreBuilder.java @@ -0,0 +1,148 @@ +package org.spongycastle.cert.jcajce; + +import java.security.GeneralSecurityException; +import java.security.Provider; +import java.security.cert.CRLException; +import java.security.cert.CertStore; +import java.security.cert.CertificateException; +import java.security.cert.CollectionCertStoreParameters; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.spongycastle.cert.X509CRLHolder; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.util.Store; + +/** + * Builder to create a CertStore from certificate and CRL stores. + */ +public class JcaCertStoreBuilder +{ + private List certs = new ArrayList(); + private List crls = new ArrayList(); + private Object provider; + private JcaX509CertificateConverter certificateConverter = new JcaX509CertificateConverter(); + private JcaX509CRLConverter crlConverter = new JcaX509CRLConverter(); + private String type = "Collection"; + + /** + * Add a store full of X509CertificateHolder objects. + * + * @param certStore a store of X509CertificateHolder objects. + */ + public JcaCertStoreBuilder addCertificates(Store certStore) + { + certs.addAll(certStore.getMatches(null)); + + return this; + } + + /** + * Add a single certificate. + * + * @param cert the X509 certificate holder containing the certificate. + */ + public JcaCertStoreBuilder addCertificate(X509CertificateHolder cert) + { + certs.add(cert); + + return this; + } + + /** + * Add a store full of X509CRLHolder objects. + * @param crlStore a store of X509CRLHolder objects. + */ + public JcaCertStoreBuilder addCRLs(Store crlStore) + { + crls.addAll(crlStore.getMatches(null)); + + return this; + } + + /** + * Add a single CRL. + * + * @param crl the X509 CRL holder containing the CRL. + */ + public JcaCertStoreBuilder addCRL(X509CRLHolder crl) + { + crls.add(crl); + + return this; + } + + public JcaCertStoreBuilder setProvider(String providerName) + { + certificateConverter.setProvider(providerName); + crlConverter.setProvider(providerName); + this.provider = providerName; + + return this; + } + + public JcaCertStoreBuilder setProvider(Provider provider) + { + certificateConverter.setProvider(provider); + crlConverter.setProvider(provider); + this.provider = provider; + + return this; + } + + /** + * Set the type of the CertStore generated. By default it is "Collection". + * + * @param type type of CertStore passed to CertStore.getInstance(). + * @return the current builder. + */ + public JcaCertStoreBuilder setType(String type) + { + this.type = type; + + return this; + } + + /** + * Build the CertStore from the current inputs. + * + * @return a CertStore. + * @throws GeneralSecurityException + */ + public CertStore build() + throws GeneralSecurityException + { + CollectionCertStoreParameters params = convertHolders(certificateConverter, crlConverter); + + if (provider instanceof String) + { + return CertStore.getInstance(type, params, (String)provider); + } + + if (provider instanceof Provider) + { + return CertStore.getInstance(type, params, (Provider)provider); + } + + return CertStore.getInstance(type, params); + } + + private CollectionCertStoreParameters convertHolders(JcaX509CertificateConverter certificateConverter, JcaX509CRLConverter crlConverter) + throws CertificateException, CRLException + { + List jcaObjs = new ArrayList(certs.size() + crls.size()); + + for (Iterator it = certs.iterator(); it.hasNext();) + { + jcaObjs.add(certificateConverter.getCertificate((X509CertificateHolder)it.next())); + } + + for (Iterator it = crls.iterator(); it.hasNext();) + { + jcaObjs.add(crlConverter.getCRL((X509CRLHolder)it.next())); + } + + return new CollectionCertStoreParameters(jcaObjs); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX500NameUtil.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX500NameUtil.java new file mode 100644 index 000000000..d6b6ae899 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX500NameUtil.java @@ -0,0 +1,29 @@ +package org.spongycastle.cert.jcajce; + +import java.security.cert.X509Certificate; + +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x500.X500NameStyle; + +public class JcaX500NameUtil +{ + public static X500Name getIssuer(X509Certificate certificate) + { + return X500Name.getInstance(certificate.getIssuerX500Principal().getEncoded()); + } + + public static X500Name getSubject(X509Certificate certificate) + { + return X500Name.getInstance(certificate.getSubjectX500Principal().getEncoded()); + } + + public static X500Name getIssuer(X500NameStyle style, X509Certificate certificate) + { + return X500Name.getInstance(style, certificate.getIssuerX500Principal().getEncoded()); + } + + public static X500Name getSubject(X500NameStyle style, X509Certificate certificate) + { + return X500Name.getInstance(style, certificate.getSubjectX500Principal().getEncoded()); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509AttributeCertificateHolder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509AttributeCertificateHolder.java new file mode 100644 index 000000000..350762527 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509AttributeCertificateHolder.java @@ -0,0 +1,26 @@ +package org.spongycastle.cert.jcajce; + +import java.io.IOException; + +import org.spongycastle.asn1.x509.AttributeCertificate; +import org.spongycastle.cert.X509AttributeCertificateHolder; +import org.spongycastle.x509.X509AttributeCertificate; + +/** + * JCA helper class for converting an old style X509AttributeCertificate into a X509AttributeCertificateHolder object. + */ +public class JcaX509AttributeCertificateHolder + extends X509AttributeCertificateHolder +{ + /** + * Base constructor. + * + * @param cert AttributeCertificate to be used a the source for the holder creation. + * @throws IOException if there is a problem extracting the attribute certificate information. + */ + public JcaX509AttributeCertificateHolder(X509AttributeCertificate cert) + throws IOException + { + super(AttributeCertificate.getInstance(cert.getEncoded())); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509CRLConverter.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509CRLConverter.java new file mode 100644 index 000000000..7040f1aa9 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509CRLConverter.java @@ -0,0 +1,103 @@ +package org.spongycastle.cert.jcajce; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.security.NoSuchProviderException; +import java.security.Provider; +import java.security.cert.CRLException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509CRL; + +import org.spongycastle.cert.X509CRLHolder; + +/** + * Class for converting an X509CRLHolder into a corresponding X509CRL object tied to a + * particular JCA provider. + */ +public class JcaX509CRLConverter +{ + private CertHelper helper = new DefaultCertHelper(); + + /** + * Base constructor, configure with the default provider. + */ + public JcaX509CRLConverter() + { + this.helper = new DefaultCertHelper(); + } + + /** + * Set the provider to use from a Provider object. + * + * @param provider the provider to use. + * @return the converter instance. + */ + public JcaX509CRLConverter setProvider(Provider provider) + { + this.helper = new ProviderCertHelper(provider); + + return this; + } + + /** + * Set the provider to use by name. + * + * @param providerName name of the provider to use. + * @return the converter instance. + */ + public JcaX509CRLConverter setProvider(String providerName) + { + this.helper = new NamedCertHelper(providerName); + + return this; + } + + /** + * Use the configured converter to produce a X509CRL object from a X509CRLHolder object. + * + * @param crlHolder the holder to be converted + * @return a X509CRL object + * @throws CRLException if the conversion is unable to be made. + */ + public X509CRL getCRL(X509CRLHolder crlHolder) + throws CRLException + { + try + { + CertificateFactory cFact = helper.getCertificateFactory("X.509"); + + return (X509CRL)cFact.generateCRL(new ByteArrayInputStream(crlHolder.getEncoded())); + } + catch (IOException e) + { + throw new ExCRLException("exception parsing certificate: " + e.getMessage(), e); + } + catch (NoSuchProviderException e) + { + throw new ExCRLException("cannot find required provider:" + e.getMessage(), e); + } + catch (CertificateException e) + { + throw new ExCRLException("cannot create factory: " + e.getMessage(), e); + } + } + + private class ExCRLException + extends CRLException + { + private Throwable cause; + + public ExCRLException(String msg, Throwable cause) + { + super(msg); + + this.cause = cause; + } + + public Throwable getCause() + { + return cause; + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509CRLHolder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509CRLHolder.java new file mode 100644 index 000000000..91bcd88db --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509CRLHolder.java @@ -0,0 +1,26 @@ +package org.spongycastle.cert.jcajce; + +import java.security.cert.CRLException; +import java.security.cert.X509CRL; + +import org.spongycastle.asn1.x509.CertificateList; +import org.spongycastle.cert.X509CRLHolder; + +/** + * JCA helper class for converting an X509CRL into a X509CRLHolder object. + */ +public class JcaX509CRLHolder + extends X509CRLHolder +{ + /** + * Base constructor. + * + * @param crl CRL to be used a the source for the holder creation. + * @throws CRLException if there is a problem extracting the CRL information. + */ + public JcaX509CRLHolder(X509CRL crl) + throws CRLException + { + super(CertificateList.getInstance(crl.getEncoded())); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509CertificateConverter.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509CertificateConverter.java new file mode 100644 index 000000000..5e46a17d7 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509CertificateConverter.java @@ -0,0 +1,116 @@ +package org.spongycastle.cert.jcajce; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.security.NoSuchProviderException; +import java.security.Provider; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; + +import org.spongycastle.cert.X509CertificateHolder; + +/** + * Converter for producing X509Certificate objects tied to a specific provider from X509CertificateHolder objects. + */ +public class JcaX509CertificateConverter +{ + private CertHelper helper = new DefaultCertHelper(); + + /** + * Base constructor, configure with the default provider. + */ + public JcaX509CertificateConverter() + { + this.helper = new DefaultCertHelper(); + } + + /** + * Set the provider to use from a Provider object. + * + * @param provider the provider to use. + * @return the converter instance. + */ + public JcaX509CertificateConverter setProvider(Provider provider) + { + this.helper = new ProviderCertHelper(provider); + + return this; + } + + /** + * Set the provider to use by name. + * + * @param providerName name of the provider to use. + * @return the converter instance. + */ + public JcaX509CertificateConverter setProvider(String providerName) + { + this.helper = new NamedCertHelper(providerName); + + return this; + } + + /** + * Use the configured converter to produce a X509Certificate object from a X509CertificateHolder object. + * + * @param certHolder the holder to be converted + * @return a X509Certificate object + * @throws CertificateException if the conversion is unable to be made. + */ + public X509Certificate getCertificate(X509CertificateHolder certHolder) + throws CertificateException + { + try + { + CertificateFactory cFact = helper.getCertificateFactory("X.509"); + + return (X509Certificate)cFact.generateCertificate(new ByteArrayInputStream(certHolder.getEncoded())); + } + catch (IOException e) + { + throw new ExCertificateParsingException("exception parsing certificate: " + e.getMessage(), e); + } + catch (NoSuchProviderException e) + { + throw new ExCertificateException("cannot find required provider:" + e.getMessage(), e); + } + } + + private class ExCertificateParsingException + extends CertificateParsingException + { + private Throwable cause; + + public ExCertificateParsingException(String msg, Throwable cause) + { + super(msg); + + this.cause = cause; + } + + public Throwable getCause() + { + return cause; + } + } + + private class ExCertificateException + extends CertificateException + { + private Throwable cause; + + public ExCertificateException(String msg, Throwable cause) + { + super(msg); + + this.cause = cause; + } + + public Throwable getCause() + { + return cause; + } + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509CertificateHolder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509CertificateHolder.java new file mode 100644 index 000000000..a523f975e --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509CertificateHolder.java @@ -0,0 +1,26 @@ +package org.spongycastle.cert.jcajce; + +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; + +import org.spongycastle.asn1.x509.Certificate; +import org.spongycastle.cert.X509CertificateHolder; + +/** + * JCA helper class for converting an X509Certificate into a X509CertificateHolder object. + */ +public class JcaX509CertificateHolder + extends X509CertificateHolder +{ + /** + * Base constructor. + * + * @param cert certificate to be used a the source for the holder creation. + * @throws CertificateEncodingException if there is a problem extracting the certificate information. + */ + public JcaX509CertificateHolder(X509Certificate cert) + throws CertificateEncodingException + { + super(Certificate.getInstance(cert.getEncoded())); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509ContentVerifierProviderBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509ContentVerifierProviderBuilder.java new file mode 100644 index 000000000..46eb3b432 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509ContentVerifierProviderBuilder.java @@ -0,0 +1,50 @@ +package org.spongycastle.cert.jcajce; + +import java.security.Provider; +import java.security.cert.CertificateException; + +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.cert.X509ContentVerifierProviderBuilder; +import org.spongycastle.operator.ContentVerifierProvider; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.operator.jcajce.JcaContentVerifierProviderBuilder; + +public class JcaX509ContentVerifierProviderBuilder + implements X509ContentVerifierProviderBuilder +{ + private JcaContentVerifierProviderBuilder builder = new JcaContentVerifierProviderBuilder(); + + public JcaX509ContentVerifierProviderBuilder setProvider(Provider provider) + { + this.builder.setProvider(provider); + + return this; + } + + public JcaX509ContentVerifierProviderBuilder setProvider(String providerName) + { + this.builder.setProvider(providerName); + + return this; + } + + public ContentVerifierProvider build(SubjectPublicKeyInfo validatingKeyInfo) + throws OperatorCreationException + { + return builder.build(validatingKeyInfo); + } + + public ContentVerifierProvider build(X509CertificateHolder validatingKeyInfo) + throws OperatorCreationException + { + try + { + return builder.build(validatingKeyInfo); + } + catch (CertificateException e) + { + throw new OperatorCreationException("Unable to process certificate: " + e.getMessage(), e); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509ExtensionUtils.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509ExtensionUtils.java new file mode 100644 index 000000000..12f01fbef --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509ExtensionUtils.java @@ -0,0 +1,129 @@ +package org.spongycastle.cert.jcajce; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; + +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.oiw.OIWObjectIdentifiers; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.AuthorityKeyIdentifier; +import org.spongycastle.asn1.x509.SubjectKeyIdentifier; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.cert.X509ExtensionUtils; +import org.spongycastle.operator.DigestCalculator; + +public class JcaX509ExtensionUtils + extends X509ExtensionUtils +{ + /** + * Create a utility class pre-configured with a SHA-1 digest calculator based on the + * default implementation. + * + * @throws NoSuchAlgorithmException + */ + public JcaX509ExtensionUtils() + throws NoSuchAlgorithmException + { + super(new SHA1DigestCalculator(MessageDigest.getInstance("SHA1"))); + } + + public JcaX509ExtensionUtils(DigestCalculator calculator) + { + super(calculator); + } + + public AuthorityKeyIdentifier createAuthorityKeyIdentifier( + X509Certificate cert) + throws CertificateEncodingException + { + return super.createAuthorityKeyIdentifier(new JcaX509CertificateHolder(cert)); + } + + public AuthorityKeyIdentifier createAuthorityKeyIdentifier( + PublicKey pubKey) + { + return super.createAuthorityKeyIdentifier(SubjectPublicKeyInfo.getInstance(pubKey.getEncoded())); + } + + /** + * Return a RFC 3280 type 1 key identifier. As in: + * <pre> + * (1) The keyIdentifier is composed of the 160-bit SHA-1 hash of the + * value of the BIT STRING subjectPublicKey (excluding the tag, + * length, and number of unused bits). + * </pre> + * @param publicKey the key object containing the key identifier is to be based on. + * @return the key identifier. + */ + public SubjectKeyIdentifier createSubjectKeyIdentifier( + PublicKey publicKey) + { + return super.createSubjectKeyIdentifier(SubjectPublicKeyInfo.getInstance(publicKey.getEncoded())); + } + + /** + * Return a RFC 3280 type 2 key identifier. As in: + * <pre> + * (2) The keyIdentifier is composed of a four bit type field with + * the value 0100 followed by the least significant 60 bits of the + * SHA-1 hash of the value of the BIT STRING subjectPublicKey. + * </pre> + * @param publicKey the key object of interest. + * @return the key identifier. + */ + public SubjectKeyIdentifier createTruncatedSubjectKeyIdentifier(PublicKey publicKey) + { + return super.createSubjectKeyIdentifier(SubjectPublicKeyInfo.getInstance(publicKey.getEncoded())); + } + + /** + * Return the ASN.1 object contained in a byte[] returned by a getExtensionValue() call. + * + * @param encExtValue DER encoded OCTET STRING containing the DER encoded extension object. + * @return an ASN.1 object + * @throws java.io.IOException on a parsing error. + */ + public static ASN1Primitive parseExtensionValue(byte[] encExtValue) + throws IOException + { + return ASN1Primitive.fromByteArray(ASN1OctetString.getInstance(encExtValue).getOctets()); + } + + private static class SHA1DigestCalculator + implements DigestCalculator + { + private ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + private MessageDigest digest; + + public SHA1DigestCalculator(MessageDigest digest) + { + this.digest = digest; + } + + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1); + } + + public OutputStream getOutputStream() + { + return bOut; + } + + public byte[] getDigest() + { + byte[] bytes = digest.digest(bOut.toByteArray()); + + bOut.reset(); + + return bytes; + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509v1CertificateBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509v1CertificateBuilder.java new file mode 100644 index 000000000..e5e4f83d4 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509v1CertificateBuilder.java @@ -0,0 +1,48 @@ +package org.spongycastle.cert.jcajce; + +import java.math.BigInteger; +import java.security.PublicKey; +import java.util.Date; + +import javax.security.auth.x500.X500Principal; + +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.cert.X509v1CertificateBuilder; + +/** + * JCA helper class to allow JCA objects to be used in the construction of a Version 1 certificate. + */ +public class JcaX509v1CertificateBuilder + extends X509v1CertificateBuilder +{ + /** + * Initialise the builder using a PublicKey. + * + * @param issuer X500Name representing the issuer of this certificate. + * @param serial the serial number for the certificate. + * @param notBefore date before which the certificate is not valid. + * @param notAfter date after which the certificate is not valid. + * @param subject X500Name representing the subject of this certificate. + * @param publicKey the public key to be associated with the certificate. + */ + public JcaX509v1CertificateBuilder(X500Name issuer, BigInteger serial, Date notBefore, Date notAfter, X500Name subject, PublicKey publicKey) + { + super(issuer, serial, notBefore, notAfter, subject, SubjectPublicKeyInfo.getInstance(publicKey.getEncoded())); + } + + /** + * Initialise the builder using X500Principal objects and a PublicKey. + * + * @param issuer principal representing the issuer of this certificate. + * @param serial the serial number for the certificate. + * @param notBefore date before which the certificate is not valid. + * @param notAfter date after which the certificate is not valid. + * @param subject principal representing the subject of this certificate. + * @param publicKey the public key to be associated with the certificate. + */ + public JcaX509v1CertificateBuilder(X500Principal issuer, BigInteger serial, Date notBefore, Date notAfter, X500Principal subject, PublicKey publicKey) + { + super(X500Name.getInstance(issuer.getEncoded()), serial, notBefore, notAfter, X500Name.getInstance(subject.getEncoded()), SubjectPublicKeyInfo.getInstance(publicKey.getEncoded())); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509v2CRLBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509v2CRLBuilder.java new file mode 100644 index 000000000..97d544bd3 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509v2CRLBuilder.java @@ -0,0 +1,23 @@ +package org.spongycastle.cert.jcajce; + +import java.security.cert.X509Certificate; +import java.util.Date; + +import javax.security.auth.x500.X500Principal; + +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.cert.X509v2CRLBuilder; + +public class JcaX509v2CRLBuilder + extends X509v2CRLBuilder +{ + public JcaX509v2CRLBuilder(X500Principal issuer, Date now) + { + super(X500Name.getInstance(issuer.getEncoded()), now); + } + + public JcaX509v2CRLBuilder(X509Certificate issuerCert, Date now) + { + this(issuerCert.getSubjectX500Principal(), now); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509v3CertificateBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509v3CertificateBuilder.java new file mode 100644 index 000000000..c61654c1d --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509v3CertificateBuilder.java @@ -0,0 +1,103 @@ +package org.spongycastle.cert.jcajce; + +import java.math.BigInteger; +import java.security.PublicKey; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.Date; + +import javax.security.auth.x500.X500Principal; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.cert.X509v3CertificateBuilder; + +/** + * JCA helper class to allow JCA objects to be used in the construction of a Version 3 certificate. + */ +public class JcaX509v3CertificateBuilder + extends X509v3CertificateBuilder +{ + /** + * Initialise the builder using a PublicKey. + * + * @param issuer X500Name representing the issuer of this certificate. + * @param serial the serial number for the certificate. + * @param notBefore date before which the certificate is not valid. + * @param notAfter date after which the certificate is not valid. + * @param subject X500Name representing the subject of this certificate. + * @param publicKey the public key to be associated with the certificate. + */ + public JcaX509v3CertificateBuilder(X500Name issuer, BigInteger serial, Date notBefore, Date notAfter, X500Name subject, PublicKey publicKey) + { + super(issuer, serial, notBefore, notAfter, subject, SubjectPublicKeyInfo.getInstance(publicKey.getEncoded())); + } + + /** + * Initialise the builder using X500Principal objects and a PublicKey. + * + * @param issuer principal representing the issuer of this certificate. + * @param serial the serial number for the certificate. + * @param notBefore date before which the certificate is not valid. + * @param notAfter date after which the certificate is not valid. + * @param subject principal representing the subject of this certificate. + * @param publicKey the public key to be associated with the certificate. + */ + public JcaX509v3CertificateBuilder(X500Principal issuer, BigInteger serial, Date notBefore, Date notAfter, X500Principal subject, PublicKey publicKey) + { + super(X500Name.getInstance(issuer.getEncoded()), serial, notBefore, notAfter, X500Name.getInstance(subject.getEncoded()), SubjectPublicKeyInfo.getInstance(publicKey.getEncoded())); + } + + /** + * Initialise the builder using the subject from the passed in issuerCert as the issuer, as well as + * passing through and converting the other objects provided. + * + * @param issuerCert certificate who's subject is the issuer of the certificate we are building. + * @param serial the serial number for the certificate. + * @param notBefore date before which the certificate is not valid. + * @param notAfter date after which the certificate is not valid. + * @param subject principal representing the subject of this certificate. + * @param publicKey the public key to be associated with the certificate. + */ + public JcaX509v3CertificateBuilder(X509Certificate issuerCert, BigInteger serial, Date notBefore, Date notAfter, X500Principal subject, PublicKey publicKey) + { + this(issuerCert.getSubjectX500Principal(), serial, notBefore, notAfter, subject, publicKey); + } + + /** + * Initialise the builder using the subject from the passed in issuerCert as the issuer, as well as + * passing through and converting the other objects provided. + * + * @param issuerCert certificate who's subject is the issuer of the certificate we are building. + * @param serial the serial number for the certificate. + * @param notBefore date before which the certificate is not valid. + * @param notAfter date after which the certificate is not valid. + * @param subject principal representing the subject of this certificate. + * @param publicKey the public key to be associated with the certificate. + */ + public JcaX509v3CertificateBuilder(X509Certificate issuerCert, BigInteger serial, Date notBefore, Date notAfter, X500Name subject, PublicKey publicKey) + { + this(X500Name.getInstance(issuerCert.getSubjectX500Principal().getEncoded()), serial, notBefore, notAfter, subject, publicKey); + } + + /** + * Add a given extension field for the standard extensions tag (tag 3) + * copying the extension value from another certificate. + * + * @param oid the type of the extension to be copied. + * @param critical true if the extension is to be marked critical, false otherwise. + * @param certificate the source of the extension to be copied. + * @return the builder instance. + */ + public JcaX509v3CertificateBuilder copyAndAddExtension( + ASN1ObjectIdentifier oid, + boolean critical, + X509Certificate certificate) + throws CertificateEncodingException + { + this.copyAndAddExtension(oid, critical, new JcaX509CertificateHolder(certificate)); + + return this; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/NamedCertHelper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/NamedCertHelper.java new file mode 100644 index 000000000..89584138e --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/NamedCertHelper.java @@ -0,0 +1,22 @@ +package org.spongycastle.cert.jcajce; + +import java.security.NoSuchProviderException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; + +class NamedCertHelper + extends CertHelper +{ + private final String providerName; + + NamedCertHelper(String providerName) + { + this.providerName = providerName; + } + + protected CertificateFactory createCertificateFactory(String type) + throws CertificateException, NoSuchProviderException + { + return CertificateFactory.getInstance(type, providerName); + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/ProviderCertHelper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/ProviderCertHelper.java new file mode 100644 index 000000000..ffe37e9bd --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/jcajce/ProviderCertHelper.java @@ -0,0 +1,22 @@ +package org.spongycastle.cert.jcajce; + +import java.security.Provider; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; + +class ProviderCertHelper + extends CertHelper +{ + private final Provider provider; + + ProviderCertHelper(Provider provider) + { + this.provider = provider; + } + + protected CertificateFactory createCertificateFactory(String type) + throws CertificateException + { + return CertificateFactory.getInstance(type, provider); + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/BasicOCSPResp.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/BasicOCSPResp.java new file mode 100644 index 000000000..f3c65670f --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/BasicOCSPResp.java @@ -0,0 +1,212 @@ +package org.spongycastle.cert.ocsp; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Date; +import java.util.List; +import java.util.Set; + +import org.spongycastle.asn1.ASN1Encoding; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.ocsp.BasicOCSPResponse; +import org.spongycastle.asn1.ocsp.ResponseData; +import org.spongycastle.asn1.ocsp.SingleResponse; +import org.spongycastle.asn1.x509.Certificate; +import org.spongycastle.asn1.x509.Extension; +import org.spongycastle.asn1.x509.Extensions; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.operator.ContentVerifier; +import org.spongycastle.operator.ContentVerifierProvider; + +/** + * <pre> + * BasicOCSPResponse ::= SEQUENCE { + * tbsResponseData ResponseData, + * signatureAlgorithm AlgorithmIdentifier, + * signature BIT STRING, + * certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL } + * </pre> + */ +public class BasicOCSPResp +{ + private BasicOCSPResponse resp; + private ResponseData data; + private Extensions extensions; + + public BasicOCSPResp( + BasicOCSPResponse resp) + { + this.resp = resp; + this.data = resp.getTbsResponseData(); + this.extensions = Extensions.getInstance(resp.getTbsResponseData().getResponseExtensions()); + } + + /** + * Return the DER encoding of the tbsResponseData field. + * @return DER encoding of tbsResponseData + */ + public byte[] getTBSResponseData() + { + try + { + return resp.getTbsResponseData().getEncoded(ASN1Encoding.DER); + } + catch (IOException e) + { + return null; + } + } + + public int getVersion() + { + return data.getVersion().getValue().intValue() + 1; + } + + public RespID getResponderId() + { + return new RespID(data.getResponderID()); + } + + public Date getProducedAt() + { + return OCSPUtils.extractDate(data.getProducedAt()); + } + + public SingleResp[] getResponses() + { + ASN1Sequence s = data.getResponses(); + SingleResp[] rs = new SingleResp[s.size()]; + + for (int i = 0; i != rs.length; i++) + { + rs[i] = new SingleResp(SingleResponse.getInstance(s.getObjectAt(i))); + } + + return rs; + } + + public boolean hasExtensions() + { + return extensions != null; + } + + public Extension getExtension(ASN1ObjectIdentifier oid) + { + if (extensions != null) + { + return extensions.getExtension(oid); + } + + return null; + } + + public List getExtensionOIDs() + { + return OCSPUtils.getExtensionOIDs(extensions); + } + + public Set getCriticalExtensionOIDs() + { + return OCSPUtils.getCriticalExtensionOIDs(extensions); + } + + public Set getNonCriticalExtensionOIDs() + { + return OCSPUtils.getNonCriticalExtensionOIDs(extensions); + } + + + public ASN1ObjectIdentifier getSignatureAlgOID() + { + return resp.getSignatureAlgorithm().getAlgorithm(); + } + + public byte[] getSignature() + { + return resp.getSignature().getBytes(); + } + + public X509CertificateHolder[] getCerts() + { + // + // load the certificates if we have any + // + if (resp.getCerts() != null) + { + ASN1Sequence s = resp.getCerts(); + + if (s != null) + { + X509CertificateHolder[] certs = new X509CertificateHolder[s.size()]; + + for (int i = 0; i != certs.length; i++) + { + certs[i] = new X509CertificateHolder(Certificate.getInstance(s.getObjectAt(i))); + } + + return certs; + } + + return OCSPUtils.EMPTY_CERTS; + } + else + { + return OCSPUtils.EMPTY_CERTS; + } + } + + /** + * verify the signature against the tbsResponseData object we contain. + */ + public boolean isSignatureValid( + ContentVerifierProvider verifierProvider) + throws OCSPException + { + try + { + ContentVerifier verifier = verifierProvider.get(resp.getSignatureAlgorithm()); + OutputStream vOut = verifier.getOutputStream(); + + vOut.write(resp.getTbsResponseData().getEncoded(ASN1Encoding.DER)); + vOut.close(); + + return verifier.verify(this.getSignature()); + } + catch (Exception e) + { + throw new OCSPException("exception processing sig: " + e, e); + } + } + + /** + * return the ASN.1 encoded representation of this object. + */ + public byte[] getEncoded() + throws IOException + { + return resp.getEncoded(); + } + + public boolean equals(Object o) + { + if (o == this) + { + return true; + } + + if (!(o instanceof BasicOCSPResp)) + { + return false; + } + + BasicOCSPResp r = (BasicOCSPResp)o; + + return resp.equals(r.resp); + } + + public int hashCode() + { + return resp.hashCode(); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/BasicOCSPRespBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/BasicOCSPRespBuilder.java new file mode 100644 index 000000000..32b50d1cf --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/BasicOCSPRespBuilder.java @@ -0,0 +1,264 @@ +package org.spongycastle.cert.ocsp; + +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Encoding; +import org.spongycastle.asn1.ASN1GeneralizedTime; +import org.spongycastle.asn1.DERBitString; +import org.spongycastle.asn1.DERGeneralizedTime; +import org.spongycastle.asn1.DERNull; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.asn1.ocsp.BasicOCSPResponse; +import org.spongycastle.asn1.ocsp.CertStatus; +import org.spongycastle.asn1.ocsp.ResponseData; +import org.spongycastle.asn1.ocsp.RevokedInfo; +import org.spongycastle.asn1.ocsp.SingleResponse; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.CRLReason; +import org.spongycastle.asn1.x509.Extensions; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.operator.ContentSigner; +import org.spongycastle.operator.DigestCalculator; + +/** + * Generator for basic OCSP response objects. + */ +public class BasicOCSPRespBuilder +{ + private List list = new ArrayList(); + private Extensions responseExtensions = null; + private RespID responderID; + + private class ResponseObject + { + CertificateID certId; + CertStatus certStatus; + DERGeneralizedTime thisUpdate; + DERGeneralizedTime nextUpdate; + Extensions extensions; + + public ResponseObject( + CertificateID certId, + CertificateStatus certStatus, + Date thisUpdate, + Date nextUpdate, + Extensions extensions) + { + this.certId = certId; + + if (certStatus == null) + { + this.certStatus = new CertStatus(); + } + else if (certStatus instanceof UnknownStatus) + { + this.certStatus = new CertStatus(2, DERNull.INSTANCE); + } + else + { + RevokedStatus rs = (RevokedStatus)certStatus; + + if (rs.hasRevocationReason()) + { + this.certStatus = new CertStatus( + new RevokedInfo(new ASN1GeneralizedTime(rs.getRevocationTime()), CRLReason.lookup(rs.getRevocationReason()))); + } + else + { + this.certStatus = new CertStatus( + new RevokedInfo(new ASN1GeneralizedTime(rs.getRevocationTime()), null)); + } + } + + this.thisUpdate = new DERGeneralizedTime(thisUpdate); + + if (nextUpdate != null) + { + this.nextUpdate = new DERGeneralizedTime(nextUpdate); + } + else + { + this.nextUpdate = null; + } + + this.extensions = extensions; + } + + public SingleResponse toResponse() + throws Exception + { + return new SingleResponse(certId.toASN1Object(), certStatus, thisUpdate, nextUpdate, extensions); + } + } + + /** + * basic constructor + */ + public BasicOCSPRespBuilder( + RespID responderID) + { + this.responderID = responderID; + } + + /** + * construct with the responderID to be the SHA-1 keyHash of the passed in public key. + * + * @param key the key info of the responder public key. + * @param digCalc a SHA-1 digest calculator + */ + public BasicOCSPRespBuilder( + SubjectPublicKeyInfo key, + DigestCalculator digCalc) + throws OCSPException + { + this.responderID = new RespID(key, digCalc); + } + + /** + * Add a response for a particular Certificate ID. + * + * @param certID certificate ID details + * @param certStatus status of the certificate - null if okay + */ + public BasicOCSPRespBuilder addResponse( + CertificateID certID, + CertificateStatus certStatus) + { + list.add(new ResponseObject(certID, certStatus, new Date(), null, null)); + + return this; + } + + /** + * Add a response for a particular Certificate ID. + * + * @param certID certificate ID details + * @param certStatus status of the certificate - null if okay + * @param singleExtensions optional extensions + */ + public BasicOCSPRespBuilder addResponse( + CertificateID certID, + CertificateStatus certStatus, + Extensions singleExtensions) + { + list.add(new ResponseObject(certID, certStatus, new Date(), null, singleExtensions)); + + return this; + } + + /** + * Add a response for a particular Certificate ID. + * + * @param certID certificate ID details + * @param nextUpdate date when next update should be requested + * @param certStatus status of the certificate - null if okay + * @param singleExtensions optional extensions + */ + public BasicOCSPRespBuilder addResponse( + CertificateID certID, + CertificateStatus certStatus, + Date nextUpdate, + Extensions singleExtensions) + { + list.add(new ResponseObject(certID, certStatus, new Date(), nextUpdate, singleExtensions)); + + return this; + } + + /** + * Add a response for a particular Certificate ID. + * + * @param certID certificate ID details + * @param thisUpdate date this response was valid on + * @param nextUpdate date when next update should be requested + * @param certStatus status of the certificate - null if okay + * @param singleExtensions optional extensions + */ + public BasicOCSPRespBuilder addResponse( + CertificateID certID, + CertificateStatus certStatus, + Date thisUpdate, + Date nextUpdate, + Extensions singleExtensions) + { + list.add(new ResponseObject(certID, certStatus, thisUpdate, nextUpdate, singleExtensions)); + + return this; + } + + /** + * Set the extensions for the response. + * + * @param responseExtensions the extension object to carry. + */ + public BasicOCSPRespBuilder setResponseExtensions( + Extensions responseExtensions) + { + this.responseExtensions = responseExtensions; + + return this; + } + + public BasicOCSPResp build( + ContentSigner signer, + X509CertificateHolder[] chain, + Date producedAt) + throws OCSPException + { + Iterator it = list.iterator(); + + ASN1EncodableVector responses = new ASN1EncodableVector(); + + while (it.hasNext()) + { + try + { + responses.add(((ResponseObject)it.next()).toResponse()); + } + catch (Exception e) + { + throw new OCSPException("exception creating Request", e); + } + } + + ResponseData tbsResp = new ResponseData(responderID.toASN1Object(), new ASN1GeneralizedTime(producedAt), new DERSequence(responses), responseExtensions); + DERBitString bitSig; + + try + { + OutputStream sigOut = signer.getOutputStream(); + + sigOut.write(tbsResp.getEncoded(ASN1Encoding.DER)); + sigOut.close(); + + bitSig = new DERBitString(signer.getSignature()); + } + catch (Exception e) + { + throw new OCSPException("exception processing TBSRequest: " + e.getMessage(), e); + } + + AlgorithmIdentifier sigAlgId = signer.getAlgorithmIdentifier(); + + DERSequence chainSeq = null; + if (chain != null && chain.length > 0) + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + for (int i = 0; i != chain.length; i++) + { + v.add(chain[i].toASN1Structure()); + } + + chainSeq = new DERSequence(v); + } + + return new BasicOCSPResp(new BasicOCSPResponse(tbsResp, sigAlgId, bitSig, chainSeq)); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/CertificateID.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/CertificateID.java new file mode 100644 index 000000000..aac029ca5 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/CertificateID.java @@ -0,0 +1,156 @@ +package org.spongycastle.cert.ocsp; + +import java.io.OutputStream; +import java.math.BigInteger; + +import org.spongycastle.asn1.ASN1Encoding; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.DERNull; +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.ocsp.CertID; +import org.spongycastle.asn1.oiw.OIWObjectIdentifiers; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.operator.DigestCalculator; +import org.spongycastle.operator.DigestCalculatorProvider; +import org.spongycastle.operator.OperatorCreationException; + +public class CertificateID +{ + public static final AlgorithmIdentifier HASH_SHA1 = new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, DERNull.INSTANCE); + + private final CertID id; + + public CertificateID( + CertID id) + { + if (id == null) + { + throw new IllegalArgumentException("'id' cannot be null"); + } + this.id = id; + } + + /** + * create from an issuer certificate and the serial number of the + * certificate it signed. + * + * @param issuerCert issuing certificate + * @param number serial number + * + * @exception OCSPException if any problems occur creating the id fields. + */ + public CertificateID( + DigestCalculator digestCalculator, X509CertificateHolder issuerCert, + BigInteger number) + throws OCSPException + { + this.id = createCertID(digestCalculator, issuerCert, new ASN1Integer(number)); + } + + public ASN1ObjectIdentifier getHashAlgOID() + { + return id.getHashAlgorithm().getAlgorithm(); + } + + public byte[] getIssuerNameHash() + { + return id.getIssuerNameHash().getOctets(); + } + + public byte[] getIssuerKeyHash() + { + return id.getIssuerKeyHash().getOctets(); + } + + /** + * return the serial number for the certificate associated + * with this request. + */ + public BigInteger getSerialNumber() + { + return id.getSerialNumber().getValue(); + } + + public boolean matchesIssuer(X509CertificateHolder issuerCert, DigestCalculatorProvider digCalcProvider) + throws OCSPException + { + try + { + return createCertID(digCalcProvider.get(id.getHashAlgorithm()), issuerCert, id.getSerialNumber()).equals(id); + } + catch (OperatorCreationException e) + { + throw new OCSPException("unable to create digest calculator: " + e.getMessage(), e); + } + } + + public CertID toASN1Object() + { + return id; + } + + public boolean equals( + Object o) + { + if (!(o instanceof CertificateID)) + { + return false; + } + + CertificateID obj = (CertificateID)o; + + return id.toASN1Primitive().equals(obj.id.toASN1Primitive()); + } + + public int hashCode() + { + return id.toASN1Primitive().hashCode(); + } + + /** + * Create a new CertificateID for a new serial number derived from a previous one + * calculated for the same CA certificate. + * + * @param original the previously calculated CertificateID for the CA. + * @param newSerialNumber the serial number for the new certificate of interest. + * + * @return a new CertificateID for newSerialNumber + */ + public static CertificateID deriveCertificateID(CertificateID original, BigInteger newSerialNumber) + { + return new CertificateID(new CertID(original.id.getHashAlgorithm(), original.id.getIssuerNameHash(), original.id.getIssuerKeyHash(), new ASN1Integer(newSerialNumber))); + } + + private static CertID createCertID(DigestCalculator digCalc, X509CertificateHolder issuerCert, ASN1Integer serialNumber) + throws OCSPException + { + try + { + OutputStream dgOut = digCalc.getOutputStream(); + + dgOut.write(issuerCert.toASN1Structure().getSubject().getEncoded(ASN1Encoding.DER)); + dgOut.close(); + + ASN1OctetString issuerNameHash = new DEROctetString(digCalc.getDigest()); + + SubjectPublicKeyInfo info = issuerCert.getSubjectPublicKeyInfo(); + + dgOut = digCalc.getOutputStream(); + + dgOut.write(info.getPublicKeyData().getBytes()); + dgOut.close(); + + ASN1OctetString issuerKeyHash = new DEROctetString(digCalc.getDigest()); + + return new CertID(digCalc.getAlgorithmIdentifier(), issuerNameHash, issuerKeyHash, serialNumber); + } + catch (Exception e) + { + throw new OCSPException("problem creating ID: " + e, e); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/CertificateStatus.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/CertificateStatus.java new file mode 100644 index 000000000..ba84b8f88 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/CertificateStatus.java @@ -0,0 +1,6 @@ +package org.spongycastle.cert.ocsp; + +public interface CertificateStatus +{ + public static final CertificateStatus GOOD = null; +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPException.java new file mode 100644 index 000000000..be91e3d8e --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPException.java @@ -0,0 +1,27 @@ +package org.spongycastle.cert.ocsp; + +public class OCSPException + extends Exception +{ + private Throwable cause; + + public OCSPException( + String name) + { + super(name); + } + + public OCSPException( + String name, + Throwable cause) + { + super(name); + + this.cause = cause; + } + + public Throwable getCause() + { + return cause; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPReq.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPReq.java new file mode 100644 index 000000000..170896733 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPReq.java @@ -0,0 +1,259 @@ +package org.spongycastle.cert.ocsp; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; +import java.util.Set; + +import org.spongycastle.asn1.ASN1Encoding; +import org.spongycastle.asn1.ASN1Exception; +import org.spongycastle.asn1.ASN1InputStream; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1OutputStream; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.ocsp.OCSPRequest; +import org.spongycastle.asn1.ocsp.Request; +import org.spongycastle.asn1.x509.Certificate; +import org.spongycastle.asn1.x509.Extension; +import org.spongycastle.asn1.x509.Extensions; +import org.spongycastle.asn1.x509.GeneralName; +import org.spongycastle.cert.CertIOException; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.operator.ContentVerifier; +import org.spongycastle.operator.ContentVerifierProvider; + +/** + * <pre> + * OCSPRequest ::= SEQUENCE { + * tbsRequest TBSRequest, + * optionalSignature [0] EXPLICIT Signature OPTIONAL } + * + * TBSRequest ::= SEQUENCE { + * version [0] EXPLICIT Version DEFAULT v1, + * requestorName [1] EXPLICIT GeneralName OPTIONAL, + * requestList SEQUENCE OF Request, + * requestExtensions [2] EXPLICIT Extensions OPTIONAL } + * + * Signature ::= SEQUENCE { + * signatureAlgorithm AlgorithmIdentifier, + * signature BIT STRING, + * certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL} + * + * Version ::= INTEGER { v1(0) } + * + * Request ::= SEQUENCE { + * reqCert CertID, + * singleRequestExtensions [0] EXPLICIT Extensions OPTIONAL } + * + * CertID ::= SEQUENCE { + * hashAlgorithm AlgorithmIdentifier, + * issuerNameHash OCTET STRING, -- Hash of Issuer's DN + * issuerKeyHash OCTET STRING, -- Hash of Issuers public key + * serialNumber CertificateSerialNumber } + * </pre> + */ +public class OCSPReq +{ + private static final X509CertificateHolder[] EMPTY_CERTS = new X509CertificateHolder[0]; + + private OCSPRequest req; + private Extensions extensions; + + public OCSPReq( + OCSPRequest req) + { + this.req = req; + this.extensions = req.getTbsRequest().getRequestExtensions(); + } + + public OCSPReq( + byte[] req) + throws IOException + { + this(new ASN1InputStream(req)); + } + + private OCSPReq( + ASN1InputStream aIn) + throws IOException + { + try + { + this.req = OCSPRequest.getInstance(aIn.readObject()); + if (req == null) + { + throw new CertIOException("malformed request: no request data found"); + } + this.extensions = req.getTbsRequest().getRequestExtensions(); + } + catch (IllegalArgumentException e) + { + throw new CertIOException("malformed request: " + e.getMessage(), e); + } + catch (ClassCastException e) + { + throw new CertIOException("malformed request: " + e.getMessage(), e); + } + catch (ASN1Exception e) + { + throw new CertIOException("malformed request: " + e.getMessage(), e); + } + } + + public int getVersionNumber() + { + return req.getTbsRequest().getVersion().getValue().intValue() + 1; + } + + public GeneralName getRequestorName() + { + return GeneralName.getInstance(req.getTbsRequest().getRequestorName()); + } + + public Req[] getRequestList() + { + ASN1Sequence seq = req.getTbsRequest().getRequestList(); + Req[] requests = new Req[seq.size()]; + + for (int i = 0; i != requests.length; i++) + { + requests[i] = new Req(Request.getInstance(seq.getObjectAt(i))); + } + + return requests; + } + + public boolean hasExtensions() + { + return extensions != null; + } + + public Extension getExtension(ASN1ObjectIdentifier oid) + { + if (extensions != null) + { + return extensions.getExtension(oid); + } + + return null; + } + + public List getExtensionOIDs() + { + return OCSPUtils.getExtensionOIDs(extensions); + } + + public Set getCriticalExtensionOIDs() + { + return OCSPUtils.getCriticalExtensionOIDs(extensions); + } + + public Set getNonCriticalExtensionOIDs() + { + return OCSPUtils.getNonCriticalExtensionOIDs(extensions); + } + + /** + * return the object identifier representing the signature algorithm + */ + public ASN1ObjectIdentifier getSignatureAlgOID() + { + if (!this.isSigned()) + { + return null; + } + + return req.getOptionalSignature().getSignatureAlgorithm().getAlgorithm(); + } + + public byte[] getSignature() + { + if (!this.isSigned()) + { + return null; + } + + return req.getOptionalSignature().getSignature().getBytes(); + } + + public X509CertificateHolder[] getCerts() + { + // + // load the certificates if we have any + // + if (req.getOptionalSignature() != null) + { + ASN1Sequence s = req.getOptionalSignature().getCerts(); + + if (s != null) + { + X509CertificateHolder[] certs = new X509CertificateHolder[s.size()]; + + for (int i = 0; i != certs.length; i++) + { + certs[i] = new X509CertificateHolder(Certificate.getInstance(s.getObjectAt(i))); + } + + return certs; + } + + return EMPTY_CERTS; + } + else + { + return EMPTY_CERTS; + } + } + + /** + * Return whether or not this request is signed. + * + * @return true if signed false otherwise. + */ + public boolean isSigned() + { + return req.getOptionalSignature() != null; + } + + /** + * verify the signature against the TBSRequest object we contain. + */ + public boolean isSignatureValid( + ContentVerifierProvider verifierProvider) + throws OCSPException + { + if (!this.isSigned()) + { + throw new OCSPException("attempt to verify signature on unsigned object"); + } + + try + { + ContentVerifier verifier = verifierProvider.get(req.getOptionalSignature().getSignatureAlgorithm()); + OutputStream sOut = verifier.getOutputStream(); + + sOut.write(req.getTbsRequest().getEncoded(ASN1Encoding.DER)); + + return verifier.verify(this.getSignature()); + } + catch (Exception e) + { + throw new OCSPException("exception processing signature: " + e, e); + } + } + + /** + * return the ASN.1 encoded representation of this object. + */ + public byte[] getEncoded() + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + ASN1OutputStream aOut = new ASN1OutputStream(bOut); + + aOut.writeObject(req); + + return bOut.toByteArray(); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPReqBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPReqBuilder.java new file mode 100644 index 000000000..2bc0a6d1d --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPReqBuilder.java @@ -0,0 +1,199 @@ +package org.spongycastle.cert.ocsp; + +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Encoding; +import org.spongycastle.asn1.DERBitString; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.asn1.ocsp.OCSPRequest; +import org.spongycastle.asn1.ocsp.Request; +import org.spongycastle.asn1.ocsp.Signature; +import org.spongycastle.asn1.ocsp.TBSRequest; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.Extensions; +import org.spongycastle.asn1.x509.GeneralName; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.operator.ContentSigner; + +public class OCSPReqBuilder +{ + private List list = new ArrayList(); + private GeneralName requestorName = null; + private Extensions requestExtensions = null; + + private class RequestObject + { + CertificateID certId; + Extensions extensions; + + public RequestObject( + CertificateID certId, + Extensions extensions) + { + this.certId = certId; + this.extensions = extensions; + } + + public Request toRequest() + throws Exception + { + return new Request(certId.toASN1Object(), extensions); + } + } + + /** + * Add a request for the given CertificateID. + * + * @param certId certificate ID of interest + */ + public OCSPReqBuilder addRequest( + CertificateID certId) + { + list.add(new RequestObject(certId, null)); + + return this; + } + + /** + * Add a request with extensions + * + * @param certId certificate ID of interest + * @param singleRequestExtensions the extensions to attach to the request + */ + public OCSPReqBuilder addRequest( + CertificateID certId, + Extensions singleRequestExtensions) + { + list.add(new RequestObject(certId, singleRequestExtensions)); + + return this; + } + + /** + * Set the requestor name to the passed in X500Principal + * + * @param requestorName a X500Principal representing the requestor name. + */ + public OCSPReqBuilder setRequestorName( + X500Name requestorName) + { + this.requestorName = new GeneralName(GeneralName.directoryName, requestorName); + + return this; + } + + public OCSPReqBuilder setRequestorName( + GeneralName requestorName) + { + this.requestorName = requestorName; + + return this; + } + + public OCSPReqBuilder setRequestExtensions( + Extensions requestExtensions) + { + this.requestExtensions = requestExtensions; + + return this; + } + + private OCSPReq generateRequest( + ContentSigner contentSigner, + X509CertificateHolder[] chain) + throws OCSPException + { + Iterator it = list.iterator(); + + ASN1EncodableVector requests = new ASN1EncodableVector(); + + while (it.hasNext()) + { + try + { + requests.add(((RequestObject)it.next()).toRequest()); + } + catch (Exception e) + { + throw new OCSPException("exception creating Request", e); + } + } + + TBSRequest tbsReq = new TBSRequest(requestorName, new DERSequence(requests), requestExtensions); + + Signature signature = null; + + if (contentSigner != null) + { + if (requestorName == null) + { + throw new OCSPException("requestorName must be specified if request is signed."); + } + + try + { + OutputStream sOut = contentSigner.getOutputStream(); + + sOut.write(tbsReq.getEncoded(ASN1Encoding.DER)); + + sOut.close(); + } + catch (Exception e) + { + throw new OCSPException("exception processing TBSRequest: " + e, e); + } + + DERBitString bitSig = new DERBitString(contentSigner.getSignature()); + + AlgorithmIdentifier sigAlgId = contentSigner.getAlgorithmIdentifier(); + + if (chain != null && chain.length > 0) + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + for (int i = 0; i != chain.length; i++) + { + v.add(chain[i].toASN1Structure()); + } + + signature = new Signature(sigAlgId, bitSig, new DERSequence(v)); + } + else + { + signature = new Signature(sigAlgId, bitSig); + } + } + + return new OCSPReq(new OCSPRequest(tbsReq, signature)); + } + + /** + * Generate an unsigned request + * + * @return the OCSPReq + * @throws org.spongycastle.ocsp.OCSPException + */ + public OCSPReq build() + throws OCSPException + { + return generateRequest(null, null); + } + + public OCSPReq build( + ContentSigner signer, + X509CertificateHolder[] chain) + throws OCSPException, IllegalArgumentException + { + if (signer == null) + { + throw new IllegalArgumentException("no signer specified"); + } + + return generateRequest(signer, chain); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPResp.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPResp.java new file mode 100644 index 000000000..0b587f70f --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPResp.java @@ -0,0 +1,141 @@ +package org.spongycastle.cert.ocsp; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.spongycastle.asn1.ASN1Exception; +import org.spongycastle.asn1.ASN1InputStream; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.ocsp.BasicOCSPResponse; +import org.spongycastle.asn1.ocsp.OCSPObjectIdentifiers; +import org.spongycastle.asn1.ocsp.OCSPResponse; +import org.spongycastle.asn1.ocsp.ResponseBytes; +import org.spongycastle.cert.CertIOException; + +public class OCSPResp +{ + public static final int SUCCESSFUL = 0; // Response has valid confirmations + public static final int MALFORMED_REQUEST = 1; // Illegal confirmation request + public static final int INTERNAL_ERROR = 2; // Internal error in issuer + public static final int TRY_LATER = 3; // Try again later + // (4) is not used + public static final int SIG_REQUIRED = 5; // Must sign the request + public static final int UNAUTHORIZED = 6; // Request unauthorized + + private OCSPResponse resp; + + public OCSPResp( + OCSPResponse resp) + { + this.resp = resp; + } + + public OCSPResp( + byte[] resp) + throws IOException + { + this(new ByteArrayInputStream(resp)); + } + + public OCSPResp( + InputStream resp) + throws IOException + { + this(new ASN1InputStream(resp)); + } + + private OCSPResp( + ASN1InputStream aIn) + throws IOException + { + try + { + this.resp = OCSPResponse.getInstance(aIn.readObject()); + } + catch (IllegalArgumentException e) + { + throw new CertIOException("malformed response: " + e.getMessage(), e); + } + catch (ClassCastException e) + { + throw new CertIOException("malformed response: " + e.getMessage(), e); + } + catch (ASN1Exception e) + { + throw new CertIOException("malformed response: " + e.getMessage(), e); + } + + if (resp == null) + { + throw new CertIOException("malformed response: no response data found"); + } + } + + public int getStatus() + { + return this.resp.getResponseStatus().getValue().intValue(); + } + + public Object getResponseObject() + throws OCSPException + { + ResponseBytes rb = this.resp.getResponseBytes(); + + if (rb == null) + { + return null; + } + + if (rb.getResponseType().equals(OCSPObjectIdentifiers.id_pkix_ocsp_basic)) + { + try + { + ASN1Primitive obj = ASN1Primitive.fromByteArray(rb.getResponse().getOctets()); + return new BasicOCSPResp(BasicOCSPResponse.getInstance(obj)); + } + catch (Exception e) + { + throw new OCSPException("problem decoding object: " + e, e); + } + } + + return rb.getResponse(); + } + + /** + * return the ASN.1 encoded representation of this object. + */ + public byte[] getEncoded() + throws IOException + { + return resp.getEncoded(); + } + + public boolean equals(Object o) + { + if (o == this) + { + return true; + } + + if (!(o instanceof OCSPResp)) + { + return false; + } + + OCSPResp r = (OCSPResp)o; + + return resp.equals(r.resp); + } + + public int hashCode() + { + return resp.hashCode(); + } + + public OCSPResponse toASN1Structure() + { + return resp; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPRespBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPRespBuilder.java new file mode 100644 index 000000000..fe2da11d2 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPRespBuilder.java @@ -0,0 +1,59 @@ +package org.spongycastle.cert.ocsp; + +import java.io.IOException; + +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.ocsp.OCSPObjectIdentifiers; +import org.spongycastle.asn1.ocsp.OCSPResponse; +import org.spongycastle.asn1.ocsp.OCSPResponseStatus; +import org.spongycastle.asn1.ocsp.ResponseBytes; + +/** + * base generator for an OCSP response - at the moment this only supports the + * generation of responses containing BasicOCSP responses. + */ +public class OCSPRespBuilder +{ + public static final int SUCCESSFUL = 0; // Response has valid confirmations + public static final int MALFORMED_REQUEST = 1; // Illegal confirmation request + public static final int INTERNAL_ERROR = 2; // Internal error in issuer + public static final int TRY_LATER = 3; // Try again later + // (4) is not used + public static final int SIG_REQUIRED = 5; // Must sign the request + public static final int UNAUTHORIZED = 6; // Request unauthorized + + public OCSPResp build( + int status, + Object response) + throws OCSPException + { + if (response == null) + { + return new OCSPResp(new OCSPResponse(new OCSPResponseStatus(status), null)); + } + + if (response instanceof BasicOCSPResp) + { + BasicOCSPResp r = (BasicOCSPResp)response; + ASN1OctetString octs; + + try + { + octs = new DEROctetString(r.getEncoded()); + } + catch (IOException e) + { + throw new OCSPException("can't encode object.", e); + } + + ResponseBytes rb = new ResponseBytes( + OCSPObjectIdentifiers.id_pkix_ocsp_basic, octs); + + return new OCSPResp(new OCSPResponse( + new OCSPResponseStatus(status), rb)); + } + + throw new OCSPException("unknown response object"); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPUtils.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPUtils.java new file mode 100644 index 000000000..a58ca0715 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPUtils.java @@ -0,0 +1,64 @@ +package org.spongycastle.cert.ocsp; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.spongycastle.asn1.ASN1GeneralizedTime; +import org.spongycastle.asn1.x509.Extensions; +import org.spongycastle.cert.X509CertificateHolder; + +class OCSPUtils +{ + static final X509CertificateHolder[] EMPTY_CERTS = new X509CertificateHolder[0]; + + static Set EMPTY_SET = Collections.unmodifiableSet(new HashSet()); + static List EMPTY_LIST = Collections.unmodifiableList(new ArrayList()); + + static Date extractDate(ASN1GeneralizedTime time) + { + try + { + return time.getDate(); + } + catch (Exception e) + { + throw new IllegalStateException("exception processing GeneralizedTime: " + e.getMessage()); + } + } + + static Set getCriticalExtensionOIDs(Extensions extensions) + { + if (extensions == null) + { + return EMPTY_SET; + } + + return Collections.unmodifiableSet(new HashSet(Arrays.asList(extensions.getCriticalExtensionOIDs()))); + } + + static Set getNonCriticalExtensionOIDs(Extensions extensions) + { + if (extensions == null) + { + return EMPTY_SET; + } + + // TODO: should probably produce a set that imposes correct ordering + return Collections.unmodifiableSet(new HashSet(Arrays.asList(extensions.getNonCriticalExtensionOIDs()))); + } + + static List getExtensionOIDs(Extensions extensions) + { + if (extensions == null) + { + return EMPTY_LIST; + } + + return Collections.unmodifiableList(Arrays.asList(extensions.getExtensionOIDs())); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/Req.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/Req.java new file mode 100644 index 000000000..52c174dd5 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/Req.java @@ -0,0 +1,25 @@ +package org.spongycastle.cert.ocsp; + +import org.spongycastle.asn1.ocsp.Request; +import org.spongycastle.asn1.x509.Extensions; + +public class Req +{ + private Request req; + + public Req( + Request req) + { + this.req = req; + } + + public CertificateID getCertID() + { + return new CertificateID(req.getReqCert()); + } + + public Extensions getSingleRequestExtensions() + { + return req.getSingleRequestExtensions(); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/RespData.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/RespData.java new file mode 100644 index 000000000..3db55a3e0 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/RespData.java @@ -0,0 +1,52 @@ +package org.spongycastle.cert.ocsp; + +import java.util.Date; + +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.ocsp.ResponseData; +import org.spongycastle.asn1.ocsp.SingleResponse; +import org.spongycastle.asn1.x509.Extensions; + +public class RespData +{ + private ResponseData data; + + public RespData( + ResponseData data) + { + this.data = data; + } + + public int getVersion() + { + return data.getVersion().getValue().intValue() + 1; + } + + public RespID getResponderId() + { + return new RespID(data.getResponderID()); + } + + public Date getProducedAt() + { + return OCSPUtils.extractDate(data.getProducedAt()); + } + + public SingleResp[] getResponses() + { + ASN1Sequence s = data.getResponses(); + SingleResp[] rs = new SingleResp[s.size()]; + + for (int i = 0; i != rs.length; i++) + { + rs[i] = new SingleResp(SingleResponse.getInstance(s.getObjectAt(i))); + } + + return rs; + } + + public Extensions getResponseExtensions() + { + return data.getResponseExtensions(); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/RespID.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/RespID.java new file mode 100644 index 000000000..7510200d5 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/RespID.java @@ -0,0 +1,89 @@ +package org.spongycastle.cert.ocsp; + +import java.io.OutputStream; + +import org.spongycastle.asn1.DERNull; +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.ocsp.ResponderID; +import org.spongycastle.asn1.oiw.OIWObjectIdentifiers; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.operator.DigestCalculator; + +/** + * Carrier for a ResponderID. + */ +public class RespID +{ + public static final AlgorithmIdentifier HASH_SHA1 = new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, DERNull.INSTANCE); + + ResponderID id; + + public RespID( + ResponderID id) + { + this.id = id; + } + + public RespID( + X500Name name) + { + this.id = new ResponderID(name); + } + + /** + * Calculate a RespID based on the public key of the responder. + * + * @param subjectPublicKeyInfo the info structure for the responder public key. + * @param digCalc a SHA-1 digest calculator. + * @throws OCSPException on exception creating ID. + */ + public RespID( + SubjectPublicKeyInfo subjectPublicKeyInfo, + DigestCalculator digCalc) + throws OCSPException + { + try + { + if (!digCalc.getAlgorithmIdentifier().equals(HASH_SHA1)) + { + throw new IllegalArgumentException("only SHA-1 can be used with RespID"); + } + + OutputStream digOut = digCalc.getOutputStream(); + + digOut.write(subjectPublicKeyInfo.getPublicKeyData().getBytes()); + digOut.close(); + + this.id = new ResponderID(new DEROctetString(digCalc.getDigest())); + } + catch (Exception e) + { + throw new OCSPException("problem creating ID: " + e, e); + } + } + + public ResponderID toASN1Object() + { + return id; + } + + public boolean equals( + Object o) + { + if (!(o instanceof RespID)) + { + return false; + } + + RespID obj = (RespID)o; + + return id.equals(obj.id); + } + + public int hashCode() + { + return id.hashCode(); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/RevokedStatus.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/RevokedStatus.java new file mode 100644 index 000000000..0842ea582 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/RevokedStatus.java @@ -0,0 +1,55 @@ +package org.spongycastle.cert.ocsp; + +import java.util.Date; + +import org.spongycastle.asn1.ASN1GeneralizedTime; +import org.spongycastle.asn1.ocsp.RevokedInfo; +import org.spongycastle.asn1.x509.CRLReason; + +/** + * wrapper for the RevokedInfo object + */ +public class RevokedStatus + implements CertificateStatus +{ + RevokedInfo info; + + public RevokedStatus( + RevokedInfo info) + { + this.info = info; + } + + public RevokedStatus( + Date revocationDate, + int reason) + { + this.info = new RevokedInfo(new ASN1GeneralizedTime(revocationDate), CRLReason.lookup(reason)); + } + + public Date getRevocationTime() + { + return OCSPUtils.extractDate(info.getRevocationTime()); + } + + public boolean hasRevocationReason() + { + return (info.getRevocationReason() != null); + } + + /** + * return the revocation reason. Note: this field is optional, test for it + * with hasRevocationReason() first. + * @return the revocation reason value. + * @exception IllegalStateException if a reason is asked for and none is avaliable + */ + public int getRevocationReason() + { + if (info.getRevocationReason() == null) + { + throw new IllegalStateException("attempt to get a reason where none is available"); + } + + return info.getRevocationReason().getValue().intValue(); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/SingleResp.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/SingleResp.java new file mode 100644 index 000000000..98beb8b3b --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/SingleResp.java @@ -0,0 +1,102 @@ +package org.spongycastle.cert.ocsp; + +import java.util.Date; +import java.util.List; +import java.util.Set; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ocsp.CertStatus; +import org.spongycastle.asn1.ocsp.RevokedInfo; +import org.spongycastle.asn1.ocsp.SingleResponse; +import org.spongycastle.asn1.x509.Extension; +import org.spongycastle.asn1.x509.Extensions; + +public class SingleResp +{ + private SingleResponse resp; + private Extensions extensions; + + public SingleResp( + SingleResponse resp) + { + this.resp = resp; + this.extensions = resp.getSingleExtensions(); + } + + public CertificateID getCertID() + { + return new CertificateID(resp.getCertID()); + } + + /** + * Return the status object for the response - null indicates good. + * + * @return the status object for the response, null if it is good. + */ + public CertificateStatus getCertStatus() + { + CertStatus s = resp.getCertStatus(); + + if (s.getTagNo() == 0) + { + return null; // good + } + else if (s.getTagNo() == 1) + { + return new RevokedStatus(RevokedInfo.getInstance(s.getStatus())); + } + + return new UnknownStatus(); + } + + public Date getThisUpdate() + { + return OCSPUtils.extractDate(resp.getThisUpdate()); + } + + /** + * return the NextUpdate value - note: this is an optional field so may + * be returned as null. + * + * @return nextUpdate, or null if not present. + */ + public Date getNextUpdate() + { + if (resp.getNextUpdate() == null) + { + return null; + } + + return OCSPUtils.extractDate(resp.getNextUpdate()); + } + + public boolean hasExtensions() + { + return extensions != null; + } + + public Extension getExtension(ASN1ObjectIdentifier oid) + { + if (extensions != null) + { + return extensions.getExtension(oid); + } + + return null; + } + + public List getExtensionOIDs() + { + return OCSPUtils.getExtensionOIDs(extensions); + } + + public Set getCriticalExtensionOIDs() + { + return OCSPUtils.getCriticalExtensionOIDs(extensions); + } + + public Set getNonCriticalExtensionOIDs() + { + return OCSPUtils.getNonCriticalExtensionOIDs(extensions); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/UnknownStatus.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/UnknownStatus.java new file mode 100644 index 000000000..42eda7217 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/UnknownStatus.java @@ -0,0 +1,12 @@ +package org.spongycastle.cert.ocsp; + +/** + * wrapper for the UnknownInfo object + */ +public class UnknownStatus + implements CertificateStatus +{ + public UnknownStatus() + { + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/jcajce/JcaBasicOCSPRespBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/jcajce/JcaBasicOCSPRespBuilder.java new file mode 100644 index 000000000..cbd591dbb --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/jcajce/JcaBasicOCSPRespBuilder.java @@ -0,0 +1,18 @@ +package org.spongycastle.cert.ocsp.jcajce; + +import java.security.PublicKey; + +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.cert.ocsp.BasicOCSPRespBuilder; +import org.spongycastle.cert.ocsp.OCSPException; +import org.spongycastle.operator.DigestCalculator; + +public class JcaBasicOCSPRespBuilder + extends BasicOCSPRespBuilder +{ + public JcaBasicOCSPRespBuilder(PublicKey key, DigestCalculator digCalc) + throws OCSPException + { + super(SubjectPublicKeyInfo.getInstance(key.getEncoded()), digCalc); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/jcajce/JcaCertificateID.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/jcajce/JcaCertificateID.java new file mode 100644 index 000000000..d59e7e04c --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/jcajce/JcaCertificateID.java @@ -0,0 +1,20 @@ +package org.spongycastle.cert.ocsp.jcajce; + +import java.math.BigInteger; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; + +import org.spongycastle.cert.jcajce.JcaX509CertificateHolder; +import org.spongycastle.cert.ocsp.CertificateID; +import org.spongycastle.cert.ocsp.OCSPException; +import org.spongycastle.operator.DigestCalculator; + +public class JcaCertificateID + extends CertificateID +{ + public JcaCertificateID(DigestCalculator digestCalculator, X509Certificate issuerCert, BigInteger number) + throws OCSPException, CertificateEncodingException + { + super(digestCalculator, new JcaX509CertificateHolder(issuerCert), number); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/jcajce/JcaRespID.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/jcajce/JcaRespID.java new file mode 100644 index 000000000..55a390be9 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/ocsp/jcajce/JcaRespID.java @@ -0,0 +1,26 @@ +package org.spongycastle.cert.ocsp.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.cert.ocsp.OCSPException; +import org.spongycastle.cert.ocsp.RespID; +import org.spongycastle.operator.DigestCalculator; + +public class JcaRespID + extends RespID +{ + public JcaRespID(X500Principal name) + { + super(X500Name.getInstance(name.getEncoded())); + } + + public JcaRespID(PublicKey pubKey, DigestCalculator digCalc) + throws OCSPException + { + super(SubjectPublicKeyInfo.getInstance(pubKey.getEncoded()), digCalc); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/CertPath.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/CertPath.java new file mode 100644 index 000000000..bbb20a4db --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/CertPath.java @@ -0,0 +1,80 @@ +package org.spongycastle.cert.path; + +import org.spongycastle.cert.X509CertificateHolder; + +public class CertPath +{ + private final X509CertificateHolder[] certificates; + + public CertPath(X509CertificateHolder[] certificates) + { + this.certificates = copyArray(certificates); + } + + public X509CertificateHolder[] getCertificates() + { + return copyArray(certificates); + } + + public CertPathValidationResult validate(CertPathValidation[] ruleSet) + { + CertPathValidationContext context = new CertPathValidationContext(CertPathUtils.getCriticalExtensionsOIDs(certificates)); + + for (int i = 0; i != ruleSet.length; i++) + { + for (int j = certificates.length - 1; j >= 0; j--) + { + try + { + context.setIsEndEntity(j == 0); + ruleSet[i].validate(context, certificates[j]); + } + catch (CertPathValidationException e) + { // TODO: introduce object to hold (i and e) + return new CertPathValidationResult(context, j, i, e); + } + } + } + + return new CertPathValidationResult(context); + } + + public CertPathValidationResult evaluate(CertPathValidation[] ruleSet) + { + CertPathValidationContext context = new CertPathValidationContext(CertPathUtils.getCriticalExtensionsOIDs(certificates)); + + CertPathValidationResultBuilder builder = new CertPathValidationResultBuilder(); + + for (int i = 0; i != ruleSet.length; i++) + { + for (int j = certificates.length - 1; j >= 0; j--) + { + try + { + context.setIsEndEntity(j == 0); + ruleSet[i].validate(context, certificates[j]); + } + catch (CertPathValidationException e) + { + builder.addException(e); + } + } + } + + return builder.build(); + } + + private X509CertificateHolder[] copyArray(X509CertificateHolder[] array) + { + X509CertificateHolder[] rv = new X509CertificateHolder[array.length]; + + System.arraycopy(array, 0, rv, 0, rv.length); + + return rv; + } + + public int length() + { + return certificates.length; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/CertPathUtils.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/CertPathUtils.java new file mode 100644 index 000000000..257e0bb88 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/CertPathUtils.java @@ -0,0 +1,21 @@ +package org.spongycastle.cert.path; + +import java.util.HashSet; +import java.util.Set; + +import org.spongycastle.cert.X509CertificateHolder; + +class CertPathUtils +{ + static Set getCriticalExtensionsOIDs(X509CertificateHolder[] certificates) + { + Set criticalExtensions = new HashSet(); + + for (int i = 0; i != certificates.length; i++) + { + criticalExtensions.addAll(certificates[i].getCriticalExtensionOIDs()); + } + + return criticalExtensions; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/CertPathValidation.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/CertPathValidation.java new file mode 100644 index 000000000..11f483670 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/CertPathValidation.java @@ -0,0 +1,11 @@ +package org.spongycastle.cert.path; + +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.util.Memoable; + +public interface CertPathValidation + extends Memoable +{ + public void validate(CertPathValidationContext context, X509CertificateHolder certificate) + throws CertPathValidationException; +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/CertPathValidationContext.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/CertPathValidationContext.java new file mode 100644 index 000000000..010b4ef69 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/CertPathValidationContext.java @@ -0,0 +1,61 @@ +package org.spongycastle.cert.path; + +import java.util.HashSet; +import java.util.Set; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.util.Memoable; + +public class CertPathValidationContext + implements Memoable +{ + private Set criticalExtensions; + + private Set handledExtensions = new HashSet(); + private boolean endEntity; + private int index; + + public CertPathValidationContext(Set criticalExtensionsOIDs) + { + this.criticalExtensions = criticalExtensionsOIDs; + } + + public void addHandledExtension(ASN1ObjectIdentifier extensionIdentifier) + { + this.handledExtensions.add(extensionIdentifier); + } + + public void setIsEndEntity(boolean isEndEntity) + { + this.endEntity = isEndEntity; + } + + public Set getUnhandledCriticalExtensionOIDs() + { + Set rv = new HashSet(criticalExtensions); + + rv.removeAll(handledExtensions); + + return rv; + } + + /** + * Returns true if the current certificate is the end-entity certificate. + * + * @return if current cert end-entity, false otherwise. + */ + public boolean isEndEntity() + { + return endEntity; + } + + public Memoable copy() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public void reset(Memoable other) + { + //To change body of implemented methods use File | Settings | File Templates. + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/CertPathValidationException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/CertPathValidationException.java new file mode 100644 index 000000000..0a1188d12 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/CertPathValidationException.java @@ -0,0 +1,24 @@ +package org.spongycastle.cert.path; + +public class CertPathValidationException + extends Exception +{ + private final Exception cause; + + public CertPathValidationException(String msg) + { + this(msg, null); + } + + public CertPathValidationException(String msg, Exception cause) + { + super(msg); + + this.cause = cause; + } + + public Throwable getCause() + { + return cause; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/CertPathValidationResult.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/CertPathValidationResult.java new file mode 100644 index 000000000..276ef8d48 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/CertPathValidationResult.java @@ -0,0 +1,66 @@ +package org.spongycastle.cert.path; + +import java.util.Collections; +import java.util.Set; + +public class CertPathValidationResult +{ + private final boolean isValid; + private final CertPathValidationException cause; + private final Set unhandledCriticalExtensionOIDs; + + private int[] certIndexes; + + public CertPathValidationResult(CertPathValidationContext context) + { + this.unhandledCriticalExtensionOIDs = Collections.unmodifiableSet(context.getUnhandledCriticalExtensionOIDs()); + this.isValid = this.unhandledCriticalExtensionOIDs.isEmpty(); + cause = null; + } + + public CertPathValidationResult(CertPathValidationContext context, int certIndex, int ruleIndex, CertPathValidationException cause) + { + this.unhandledCriticalExtensionOIDs = Collections.unmodifiableSet(context.getUnhandledCriticalExtensionOIDs()); + this.isValid = false; + this.cause = cause; + } + + public CertPathValidationResult(CertPathValidationContext context, int[] certIndexes, int[] ruleIndexes, CertPathValidationException[] cause) + { + // TODO + this.unhandledCriticalExtensionOIDs = Collections.unmodifiableSet(context.getUnhandledCriticalExtensionOIDs()); + this.isValid = false; + this.cause = cause[0]; + this.certIndexes = certIndexes; + } + + public boolean isValid() + { + return isValid; + } + + public Exception getCause() + { + if (cause != null) + { + return cause; + } + + if (!unhandledCriticalExtensionOIDs.isEmpty()) + { + return new CertPathValidationException("Unhandled Critical Extensions"); + } + + return null; + } + + public Set getUnhandledCriticalExtensionOIDs() + { + return unhandledCriticalExtensionOIDs; + } + + public boolean isDetailed() + { + return this.certIndexes != null; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/CertPathValidationResultBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/CertPathValidationResultBuilder.java new file mode 100644 index 000000000..80bf7ff25 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/CertPathValidationResultBuilder.java @@ -0,0 +1,14 @@ +package org.spongycastle.cert.path; + +class CertPathValidationResultBuilder +{ + public CertPathValidationResult build() + { + return new CertPathValidationResult(null, 0, 0, null); + } + + public void addException(CertPathValidationException exception) + { + //To change body of created methods use File | Settings | File Templates. + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/validations/BasicConstraintsValidation.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/validations/BasicConstraintsValidation.java new file mode 100644 index 000000000..bfe0f1a4d --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/validations/BasicConstraintsValidation.java @@ -0,0 +1,103 @@ +package org.spongycastle.cert.path.validations; + +import java.math.BigInteger; + +import org.spongycastle.asn1.x509.BasicConstraints; +import org.spongycastle.asn1.x509.Extension; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.cert.path.CertPathValidation; +import org.spongycastle.cert.path.CertPathValidationContext; +import org.spongycastle.cert.path.CertPathValidationException; +import org.spongycastle.util.Memoable; + +public class BasicConstraintsValidation + implements CertPathValidation +{ + private boolean isMandatory; + private BasicConstraints bc; + private int maxPathLength; + + public BasicConstraintsValidation() + { + this(true); + } + + public BasicConstraintsValidation(boolean isMandatory) + { + this.isMandatory = isMandatory; + } + + public void validate(CertPathValidationContext context, X509CertificateHolder certificate) + throws CertPathValidationException + { + if (maxPathLength < 0) + { + throw new CertPathValidationException("BasicConstraints path length exceeded"); + } + + context.addHandledExtension(Extension.basicConstraints); + + BasicConstraints certBC = BasicConstraints.fromExtensions(certificate.getExtensions()); + + if (certBC != null) + { + if (bc != null) + { + if (certBC.isCA()) + { + BigInteger pathLengthConstraint = certBC.getPathLenConstraint(); + + if (pathLengthConstraint != null) + { + int plc = pathLengthConstraint.intValue(); + + if (plc < maxPathLength) + { + maxPathLength = plc; + bc = certBC; + } + } + } + } + else + { + bc = certBC; + if (certBC.isCA()) + { + maxPathLength = certBC.getPathLenConstraint().intValue(); + } + } + } + else + { + if (bc != null) + { + maxPathLength--; + } + } + + if (isMandatory && bc == null) + { + throw new CertPathValidationException("BasicConstraints not present in path"); + } + } + + public Memoable copy() + { + BasicConstraintsValidation v = new BasicConstraintsValidation(isMandatory); + + v.bc = this.bc; + v.maxPathLength = this.maxPathLength; + + return v; + } + + public void reset(Memoable other) + { + BasicConstraintsValidation v = (BasicConstraintsValidation)other; + + this.isMandatory = v.isMandatory; + this.bc = v.bc; + this.maxPathLength = v.maxPathLength; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/validations/CRLValidation.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/validations/CRLValidation.java new file mode 100644 index 000000000..325126e1a --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/validations/CRLValidation.java @@ -0,0 +1,78 @@ +package org.spongycastle.cert.path.validations; + +import java.util.Collection; +import java.util.Iterator; + +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.cert.X509CRLHolder; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.cert.path.CertPathValidation; +import org.spongycastle.cert.path.CertPathValidationContext; +import org.spongycastle.cert.path.CertPathValidationException; +import org.spongycastle.util.Memoable; +import org.spongycastle.util.Selector; +import org.spongycastle.util.Store; + +public class CRLValidation + implements CertPathValidation +{ + private Store crls; + private X500Name workingIssuerName; + + public CRLValidation(X500Name trustAnchorName, Store crls) + { + this.workingIssuerName = trustAnchorName; + this.crls = crls; + } + + public void validate(CertPathValidationContext context, X509CertificateHolder certificate) + throws CertPathValidationException + { + // TODO: add handling of delta CRLs + Collection matches = crls.getMatches(new Selector() + { + public boolean match(Object obj) + { + X509CRLHolder crl = (X509CRLHolder)obj; + + return (crl.getIssuer().equals(workingIssuerName)); + } + + public Object clone() + { + return this; + } + }); + + if (matches.isEmpty()) + { + throw new CertPathValidationException("CRL for " + workingIssuerName + " not found"); + } + + for (Iterator it = matches.iterator(); it.hasNext();) + { + X509CRLHolder crl = (X509CRLHolder)it.next(); + + // TODO: not quite right! + if (crl.getRevokedCertificate(certificate.getSerialNumber()) != null) + { + throw new CertPathValidationException("Certificate revoked"); + } + } + + this.workingIssuerName = certificate.getSubject(); + } + + public Memoable copy() + { + return new CRLValidation(workingIssuerName, crls); + } + + public void reset(Memoable other) + { + CRLValidation v = (CRLValidation)other; + + this.workingIssuerName = v.workingIssuerName; + this.crls = v.crls; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/validations/CertificatePoliciesValidation.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/validations/CertificatePoliciesValidation.java new file mode 100644 index 000000000..7015adb05 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/validations/CertificatePoliciesValidation.java @@ -0,0 +1,146 @@ +package org.spongycastle.cert.path.validations; + +import java.math.BigInteger; + +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.x509.Extension; +import org.spongycastle.asn1.x509.PolicyConstraints; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.cert.path.CertPathValidation; +import org.spongycastle.cert.path.CertPathValidationContext; +import org.spongycastle.cert.path.CertPathValidationException; +import org.spongycastle.util.Memoable; + +public class CertificatePoliciesValidation + implements CertPathValidation +{ + private int explicitPolicy; + private int policyMapping; + private int inhibitAnyPolicy; + + CertificatePoliciesValidation(int pathLength) + { + this(pathLength, false, false, false); + } + + CertificatePoliciesValidation(int pathLength, boolean isExplicitPolicyRequired, boolean isAnyPolicyInhibited, boolean isPolicyMappingInhibited) + { + // + // (d) + // + + if (isExplicitPolicyRequired) + { + explicitPolicy = 0; + } + else + { + explicitPolicy = pathLength + 1; + } + + // + // (e) + // + if (isAnyPolicyInhibited) + { + inhibitAnyPolicy = 0; + } + else + { + inhibitAnyPolicy = pathLength + 1; + } + + // + // (f) + // + if (isPolicyMappingInhibited) + { + policyMapping = 0; + } + else + { + policyMapping = pathLength + 1; + } + } + + public void validate(CertPathValidationContext context, X509CertificateHolder certificate) + throws CertPathValidationException + { + context.addHandledExtension(Extension.policyConstraints); + context.addHandledExtension(Extension.inhibitAnyPolicy); + + if (!context.isEndEntity()) + { + if (!ValidationUtils.isSelfIssued(certificate)) + { + // + // H (1), (2), (3) + // + explicitPolicy = countDown(explicitPolicy); + policyMapping = countDown(policyMapping); + inhibitAnyPolicy = countDown(inhibitAnyPolicy); + + // + // I (1), (2) + // + PolicyConstraints policyConstraints = PolicyConstraints.fromExtensions(certificate.getExtensions()); + + if (policyConstraints != null) + { + BigInteger requireExplicitPolicyMapping = policyConstraints.getRequireExplicitPolicyMapping(); + if (requireExplicitPolicyMapping != null) + { + if (requireExplicitPolicyMapping.intValue() < explicitPolicy) + { + explicitPolicy = requireExplicitPolicyMapping.intValue(); + } + } + + BigInteger inhibitPolicyMapping = policyConstraints.getInhibitPolicyMapping(); + if (inhibitPolicyMapping != null) + { + if (inhibitPolicyMapping.intValue() < policyMapping) + { + policyMapping = inhibitPolicyMapping.intValue(); + } + } + } + + // + // J + // + Extension ext = certificate.getExtension(Extension.inhibitAnyPolicy); + + if (ext != null) + { + int extValue = ASN1Integer.getInstance(ext.getParsedValue()).getValue().intValue(); + + if (extValue < inhibitAnyPolicy) + { + inhibitAnyPolicy = extValue; + } + } + } + } + } + + private int countDown(int policyCounter) + { + if (policyCounter != 0) + { + return policyCounter - 1; + } + + return 0; + } + + public Memoable copy() + { + return new CertificatePoliciesValidation(0); // TODO: + } + + public void reset(Memoable other) + { + CertificatePoliciesValidation v = (CertificatePoliciesValidation)other; // TODO: + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/validations/CertificatePoliciesValidationBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/validations/CertificatePoliciesValidationBuilder.java new file mode 100644 index 000000000..44817b61e --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/validations/CertificatePoliciesValidationBuilder.java @@ -0,0 +1,35 @@ +package org.spongycastle.cert.path.validations; + +import org.spongycastle.cert.path.CertPath; + +public class CertificatePoliciesValidationBuilder +{ + private boolean isExplicitPolicyRequired; + private boolean isAnyPolicyInhibited; + private boolean isPolicyMappingInhibited; + + public void setAnyPolicyInhibited(boolean anyPolicyInhibited) + { + isAnyPolicyInhibited = anyPolicyInhibited; + } + + public void setExplicitPolicyRequired(boolean explicitPolicyRequired) + { + isExplicitPolicyRequired = explicitPolicyRequired; + } + + public void setPolicyMappingInhibited(boolean policyMappingInhibited) + { + isPolicyMappingInhibited = policyMappingInhibited; + } + + public CertificatePoliciesValidation build(int pathLen) + { + return new CertificatePoliciesValidation(pathLen, isExplicitPolicyRequired, isAnyPolicyInhibited, isPolicyMappingInhibited); + } + + public CertificatePoliciesValidation build(CertPath path) + { + return build(path.length()); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/validations/KeyUsageValidation.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/validations/KeyUsageValidation.java new file mode 100644 index 000000000..7211b7cd9 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/validations/KeyUsageValidation.java @@ -0,0 +1,63 @@ +package org.spongycastle.cert.path.validations; + +import org.spongycastle.asn1.x509.Extension; +import org.spongycastle.asn1.x509.KeyUsage; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.cert.path.CertPathValidation; +import org.spongycastle.cert.path.CertPathValidationContext; +import org.spongycastle.cert.path.CertPathValidationException; +import org.spongycastle.util.Memoable; + +public class KeyUsageValidation + implements CertPathValidation +{ + private boolean isMandatory; + + public KeyUsageValidation() + { + this(true); + } + + public KeyUsageValidation(boolean isMandatory) + { + this.isMandatory = isMandatory; + } + + public void validate(CertPathValidationContext context, X509CertificateHolder certificate) + throws CertPathValidationException + { + context.addHandledExtension(Extension.keyUsage); + + if (!context.isEndEntity()) + { + KeyUsage usage = KeyUsage.fromExtensions(certificate.getExtensions()); + + if (usage != null) + { + if (!usage.hasUsages(KeyUsage.keyCertSign)) + { + throw new CertPathValidationException("Issuer certificate KeyUsage extension does not permit key signing"); + } + } + else + { + if (isMandatory) + { + throw new CertPathValidationException("KeyUsage extension not present in CA certificate"); + } + } + } + } + + public Memoable copy() + { + return new KeyUsageValidation(isMandatory); + } + + public void reset(Memoable other) + { + KeyUsageValidation v = (KeyUsageValidation)other; + + this.isMandatory = v.isMandatory; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/validations/ParentCertIssuedValidation.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/validations/ParentCertIssuedValidation.java new file mode 100644 index 000000000..dff47fb77 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/validations/ParentCertIssuedValidation.java @@ -0,0 +1,127 @@ +package org.spongycastle.cert.path.validations; + +import java.io.IOException; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1Null; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.cert.CertException; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.cert.X509ContentVerifierProviderBuilder; +import org.spongycastle.cert.path.CertPathValidation; +import org.spongycastle.cert.path.CertPathValidationContext; +import org.spongycastle.cert.path.CertPathValidationException; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.util.Memoable; + +public class ParentCertIssuedValidation + implements CertPathValidation +{ + private X509ContentVerifierProviderBuilder contentVerifierProvider; + + private X500Name workingIssuerName; + private SubjectPublicKeyInfo workingPublicKey; + private AlgorithmIdentifier workingAlgId; + + public ParentCertIssuedValidation(X509ContentVerifierProviderBuilder contentVerifierProvider) + { + this.contentVerifierProvider = contentVerifierProvider; + } + + public void validate(CertPathValidationContext context, X509CertificateHolder certificate) + throws CertPathValidationException + { + if (workingIssuerName != null) + { + if (!workingIssuerName.equals(certificate.getIssuer())) + { + throw new CertPathValidationException("Certificate issue does not match parent"); + } + } + + if (workingPublicKey != null) + { + try + { + SubjectPublicKeyInfo validatingKeyInfo; + + if (workingPublicKey.getAlgorithm().equals(workingAlgId)) + { + validatingKeyInfo = workingPublicKey; + } + else + { + validatingKeyInfo = new SubjectPublicKeyInfo(workingAlgId, workingPublicKey.parsePublicKey()); + } + + if (!certificate.isSignatureValid(contentVerifierProvider.build(validatingKeyInfo))) + { + throw new CertPathValidationException("Certificate signature not for public key in parent"); + } + } + catch (OperatorCreationException e) + { + throw new CertPathValidationException("Unable to create verifier: " + e.getMessage(), e); + } + catch (CertException e) + { + throw new CertPathValidationException("Unable to validate signature: " + e.getMessage(), e); + } + catch (IOException e) + { + throw new CertPathValidationException("Unable to build public key: " + e.getMessage(), e); + } + } + + workingIssuerName = certificate.getSubject(); + workingPublicKey = certificate.getSubjectPublicKeyInfo(); + + if (workingAlgId != null) + { + // check for inherited parameters + if (workingPublicKey.getAlgorithm().getAlgorithm().equals(workingAlgId.getAlgorithm())) + { + if (!isNull(workingPublicKey.getAlgorithm().getParameters())) + { + workingAlgId = workingPublicKey.getAlgorithm(); + } + } + else + { + workingAlgId = workingPublicKey.getAlgorithm(); + } + } + else + { + workingAlgId = workingPublicKey.getAlgorithm(); + } + } + + private boolean isNull(ASN1Encodable obj) + { + return obj == null || obj instanceof ASN1Null; + } + + public Memoable copy() + { + ParentCertIssuedValidation v = new ParentCertIssuedValidation(contentVerifierProvider); + + v.workingAlgId = this.workingAlgId; + v.workingIssuerName = this.workingIssuerName; + v.workingPublicKey = this.workingPublicKey; + + return v; + } + + public void reset(Memoable other) + { + ParentCertIssuedValidation v = (ParentCertIssuedValidation)other; + + this.contentVerifierProvider = v.contentVerifierProvider; + this.workingAlgId = v.workingAlgId; + this.workingIssuerName = v.workingIssuerName; + this.workingPublicKey = v.workingPublicKey; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/validations/ValidationUtils.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/validations/ValidationUtils.java new file mode 100644 index 000000000..5dbf495ad --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/path/validations/ValidationUtils.java @@ -0,0 +1,11 @@ +package org.spongycastle.cert.path.validations; + +import org.spongycastle.cert.X509CertificateHolder; + +class ValidationUtils +{ + static boolean isSelfIssued(X509CertificateHolder cert) + { + return cert.getSubject().equals(cert.getIssuer()); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/selector/MSOutlookKeyIdCalculator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/selector/MSOutlookKeyIdCalculator.java new file mode 100644 index 000000000..e52e6222e --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/selector/MSOutlookKeyIdCalculator.java @@ -0,0 +1,33 @@ +package org.spongycastle.cert.selector; + +import java.io.IOException; + +import org.spongycastle.asn1.ASN1Encoding; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.digests.SHA1Digest; + +class MSOutlookKeyIdCalculator +{ + static byte[] calculateKeyId(SubjectPublicKeyInfo info) + { + Digest dig = new SHA1Digest(); // TODO: include definition of SHA-1 here + byte[] hash = new byte[dig.getDigestSize()]; + byte[] spkiEnc = new byte[0]; + try + { + spkiEnc = info.getEncoded(ASN1Encoding.DER); + } + catch (IOException e) + { + return new byte[0]; + } + + // try the outlook 2010 calculation + dig.update(spkiEnc, 0, spkiEnc.length); + + dig.doFinal(hash, 0); + + return hash; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/selector/X509AttributeCertificateHolderSelector.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/selector/X509AttributeCertificateHolderSelector.java new file mode 100644 index 000000000..3ae30b336 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/selector/X509AttributeCertificateHolderSelector.java @@ -0,0 +1,268 @@ +package org.spongycastle.cert.selector; + +import java.math.BigInteger; +import java.util.Collection; +import java.util.Date; + +import org.spongycastle.asn1.x509.Extension; +import org.spongycastle.asn1.x509.GeneralName; +import org.spongycastle.asn1.x509.Target; +import org.spongycastle.asn1.x509.TargetInformation; +import org.spongycastle.asn1.x509.Targets; +import org.spongycastle.cert.AttributeCertificateHolder; +import org.spongycastle.cert.AttributeCertificateIssuer; +import org.spongycastle.cert.X509AttributeCertificateHolder; +import org.spongycastle.util.Selector; + +/** + * This class is an <code>Selector</code> like implementation to select + * attribute certificates from a given set of criteria. + */ +public class X509AttributeCertificateHolderSelector + implements Selector +{ + + // TODO: name constraints??? + + private final AttributeCertificateHolder holder; + + private final AttributeCertificateIssuer issuer; + + private final BigInteger serialNumber; + + private final Date attributeCertificateValid; + + private final X509AttributeCertificateHolder attributeCert; + + private final Collection targetNames; + + private final Collection targetGroups; + + X509AttributeCertificateHolderSelector( + AttributeCertificateHolder holder, + AttributeCertificateIssuer issuer, + BigInteger serialNumber, + Date attributeCertificateValid, + X509AttributeCertificateHolder attributeCert, + Collection targetNames, + Collection targetGroups) + { + this.holder = holder; + this.issuer = issuer; + this.serialNumber = serialNumber; + this.attributeCertificateValid = attributeCertificateValid; + this.attributeCert = attributeCert; + this.targetNames = targetNames; + this.targetGroups = targetGroups; + } + + /** + * Decides if the given attribute certificate should be selected. + * + * @param obj The X509AttributeCertificateHolder which should be checked. + * @return <code>true</code> if the attribute certificate is a match + * <code>false</code> otherwise. + */ + public boolean match(Object obj) + { + if (!(obj instanceof X509AttributeCertificateHolder)) + { + return false; + } + + X509AttributeCertificateHolder attrCert = (X509AttributeCertificateHolder)obj; + + if (this.attributeCert != null) + { + if (!this.attributeCert.equals(attrCert)) + { + return false; + } + } + if (serialNumber != null) + { + if (!attrCert.getSerialNumber().equals(serialNumber)) + { + return false; + } + } + if (holder != null) + { + if (!attrCert.getHolder().equals(holder)) + { + return false; + } + } + if (issuer != null) + { + if (!attrCert.getIssuer().equals(issuer)) + { + return false; + } + } + + if (attributeCertificateValid != null) + { + if (!attrCert.isValidOn(attributeCertificateValid)) + { + return false; + } + } + if (!targetNames.isEmpty() || !targetGroups.isEmpty()) + { + Extension targetInfoExt = attrCert.getExtension(Extension.targetInformation); + if (targetInfoExt != null) + { + TargetInformation targetinfo; + try + { + targetinfo = TargetInformation.getInstance(targetInfoExt.getParsedValue()); + } + catch (IllegalArgumentException e) + { + return false; + } + Targets[] targetss = targetinfo.getTargetsObjects(); + if (!targetNames.isEmpty()) + { + boolean found = false; + + for (int i=0; i<targetss.length; i++) + { + Targets t = targetss[i]; + Target[] targets = t.getTargets(); + for (int j=0; j<targets.length; j++) + { + if (targetNames.contains(GeneralName.getInstance(targets[j] + .getTargetName()))) + { + found = true; + break; + } + } + } + if (!found) + { + return false; + } + } + if (!targetGroups.isEmpty()) + { + boolean found = false; + + for (int i=0; i<targetss.length; i++) + { + Targets t = targetss[i]; + Target[] targets = t.getTargets(); + for (int j=0; j<targets.length; j++) + { + if (targetGroups.contains(GeneralName.getInstance(targets[j] + .getTargetGroup()))) + { + found = true; + break; + } + } + } + if (!found) + { + return false; + } + } + } + } + return true; + } + + /** + * Returns a clone of this object. + * + * @return the clone. + */ + public Object clone() + { + X509AttributeCertificateHolderSelector sel = new X509AttributeCertificateHolderSelector( + holder, issuer, serialNumber, attributeCertificateValid, attributeCert, targetNames, targetGroups); + + return sel; + } + + /** + * Returns the attribute certificate holder which must be matched. + * + * @return Returns an X509AttributeCertificateHolder + */ + public X509AttributeCertificateHolder getAttributeCert() + { + return attributeCert; + } + + /** + * Get the criteria for the validity. + * + * @return Returns the attributeCertificateValid. + */ + public Date getAttributeCertificateValid() + { + if (attributeCertificateValid != null) + { + return new Date(attributeCertificateValid.getTime()); + } + + return null; + } + + /** + * Gets the holder. + * + * @return Returns the holder. + */ + public AttributeCertificateHolder getHolder() + { + return holder; + } + + /** + * Returns the issuer criterion. + * + * @return Returns the issuer. + */ + public AttributeCertificateIssuer getIssuer() + { + return issuer; + } + + /** + * Gets the serial number the attribute certificate must have. + * + * @return Returns the serialNumber. + */ + public BigInteger getSerialNumber() + { + return serialNumber; + } + + /** + * Gets the target names. The collection consists of GeneralName objects. + * <p> + * The returned collection is immutable. + * + * @return The collection of target names + */ + public Collection getTargetNames() + { + return targetNames; + } + + /** + * Gets the target groups. The collection consists of GeneralName objects. + * <p> + * The returned collection is immutable. + * + * @return The collection of target groups. + */ + public Collection getTargetGroups() + { + return targetGroups; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/selector/X509AttributeCertificateHolderSelectorBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/selector/X509AttributeCertificateHolderSelectorBuilder.java new file mode 100644 index 000000000..35df571d2 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/selector/X509AttributeCertificateHolderSelectorBuilder.java @@ -0,0 +1,194 @@ +package org.spongycastle.cert.selector; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.spongycastle.asn1.x509.GeneralName; +import org.spongycastle.cert.AttributeCertificateHolder; +import org.spongycastle.cert.AttributeCertificateIssuer; +import org.spongycastle.cert.X509AttributeCertificateHolder; + +/** + * This class builds selectors according to the set criteria. + */ +public class X509AttributeCertificateHolderSelectorBuilder +{ + + // TODO: name constraints??? + + private AttributeCertificateHolder holder; + + private AttributeCertificateIssuer issuer; + + private BigInteger serialNumber; + + private Date attributeCertificateValid; + + private X509AttributeCertificateHolder attributeCert; + + private Collection targetNames = new HashSet(); + + private Collection targetGroups = new HashSet(); + + public X509AttributeCertificateHolderSelectorBuilder() + { + } + + /** + * Set the attribute certificate to be matched. If <code>null</code> is + * given any will do. + * + * @param attributeCert The attribute certificate holder to set. + */ + public void setAttributeCert(X509AttributeCertificateHolder attributeCert) + { + this.attributeCert = attributeCert; + } + + /** + * Set the time, when the certificate must be valid. If <code>null</code> + * is given any will do. + * + * @param attributeCertificateValid The attribute certificate validation + * time to set. + */ + public void setAttributeCertificateValid(Date attributeCertificateValid) + { + if (attributeCertificateValid != null) + { + this.attributeCertificateValid = new Date(attributeCertificateValid + .getTime()); + } + else + { + this.attributeCertificateValid = null; + } + } + + /** + * Sets the holder. If <code>null</code> is given any will do. + * + * @param holder The holder to set. + */ + public void setHolder(AttributeCertificateHolder holder) + { + this.holder = holder; + } + + /** + * Sets the issuer the attribute certificate must have. If <code>null</code> + * is given any will do. + * + * @param issuer The issuer to set. + */ + public void setIssuer(AttributeCertificateIssuer issuer) + { + this.issuer = issuer; + } + + /** + * Sets the serial number the attribute certificate must have. If + * <code>null</code> is given any will do. + * + * @param serialNumber The serialNumber to set. + */ + public void setSerialNumber(BigInteger serialNumber) + { + this.serialNumber = serialNumber; + } + + /** + * Adds a target name criterion for the attribute certificate to the target + * information extension criteria. The <code>X509AttributeCertificateHolder</code> + * must contain at least one of the specified target names. + * <p> + * Each attribute certificate may contain a target information extension + * limiting the servers where this attribute certificate can be used. If + * this extension is not present, the attribute certificate is not targeted + * and may be accepted by any server. + * + * @param name The name as a GeneralName (not <code>null</code>) + */ + public void addTargetName(GeneralName name) + { + targetNames.add(name); + } + + /** + * Adds a collection with target names criteria. If <code>null</code> is + * given any will do. + * <p> + * The collection consists of either GeneralName objects or byte[] arrays representing + * DER encoded GeneralName structures. + * + * @param names A collection of target names. + * @throws java.io.IOException if a parsing error occurs. + * @see #addTargetName(org.spongycastle.asn1.x509.GeneralName) + */ + public void setTargetNames(Collection names) throws IOException + { + targetNames = extractGeneralNames(names); + } + + /** + * Adds a target group criterion for the attribute certificate to the target + * information extension criteria. The <code>X509AttributeCertificateHolder</code> + * must contain at least one of the specified target groups. + * <p> + * Each attribute certificate may contain a target information extension + * limiting the servers where this attribute certificate can be used. If + * this extension is not present, the attribute certificate is not targeted + * and may be accepted by any server. + * + * @param group The group as GeneralName form (not <code>null</code>) + */ + public void addTargetGroup(GeneralName group) + { + targetGroups.add(group); + } + + /** + * Adds a collection with target groups criteria. If <code>null</code> is + * given any will do. + * <p> + * The collection consists of <code>GeneralName</code> objects or <code>byte[]</code representing DER + * encoded GeneralNames. + * + * @param names A collection of target groups. + * @throws java.io.IOException if a parsing error occurs. + * @see #addTargetGroup(org.spongycastle.asn1.x509.GeneralName) + */ + public void setTargetGroups(Collection names) throws IOException + { + targetGroups = extractGeneralNames(names); + } + + private Set extractGeneralNames(Collection names) + throws IOException + { + if (names == null || names.isEmpty()) + { + return new HashSet(); + } + Set temp = new HashSet(); + for (Iterator it = names.iterator(); it.hasNext();) + { + temp.add(GeneralName.getInstance(it.next())); + } + return temp; + } + + public X509AttributeCertificateHolderSelector build() + { + X509AttributeCertificateHolderSelector sel = new X509AttributeCertificateHolderSelector( + holder, issuer, serialNumber, attributeCertificateValid, attributeCert, Collections.unmodifiableCollection(new HashSet(targetNames)), Collections.unmodifiableCollection(new HashSet(targetGroups))); + + return sel; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/selector/X509CertificateHolderSelector.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/selector/X509CertificateHolderSelector.java new file mode 100644 index 000000000..8b6b88e3d --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/selector/X509CertificateHolderSelector.java @@ -0,0 +1,152 @@ +package org.spongycastle.cert.selector; + +import java.math.BigInteger; + +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.cms.IssuerAndSerialNumber; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x509.Extension; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.util.Arrays; +import org.spongycastle.util.Selector; + +/** + * a basic index for a X509CertificateHolder class + */ +public class X509CertificateHolderSelector + implements Selector +{ + private byte[] subjectKeyId; + + private X500Name issuer; + private BigInteger serialNumber; + + /** + * Construct a selector with the value of a public key's subjectKeyId. + * + * @param subjectKeyId a subjectKeyId + */ + public X509CertificateHolderSelector(byte[] subjectKeyId) + { + this(null, null, subjectKeyId); + } + + /** + * Construct a signer ID based on the issuer and serial number of the signer's associated + * certificate. + * + * @param issuer the issuer of the signer's associated certificate. + * @param serialNumber the serial number of the signer's associated certificate. + */ + public X509CertificateHolderSelector(X500Name issuer, BigInteger serialNumber) + { + this(issuer, serialNumber, null); + } + + /** + * Construct a signer ID based on the issuer and serial number of the signer's associated + * certificate. + * + * @param issuer the issuer of the signer's associated certificate. + * @param serialNumber the serial number of the signer's associated certificate. + * @param subjectKeyId the subject key identifier to use to match the signers associated certificate. + */ + public X509CertificateHolderSelector(X500Name issuer, BigInteger serialNumber, byte[] subjectKeyId) + { + this.issuer = issuer; + this.serialNumber = serialNumber; + this.subjectKeyId = subjectKeyId; + } + + public X500Name getIssuer() + { + return issuer; + } + + public BigInteger getSerialNumber() + { + return serialNumber; + } + + public byte[] getSubjectKeyIdentifier() + { + return Arrays.clone(subjectKeyId); + } + + public int hashCode() + { + int code = Arrays.hashCode(subjectKeyId); + + if (this.serialNumber != null) + { + code ^= this.serialNumber.hashCode(); + } + + if (this.issuer != null) + { + code ^= this.issuer.hashCode(); + } + + return code; + } + + public boolean equals( + Object o) + { + if (!(o instanceof X509CertificateHolderSelector)) + { + return false; + } + + X509CertificateHolderSelector id = (X509CertificateHolderSelector)o; + + return Arrays.areEqual(subjectKeyId, id.subjectKeyId) + && equalsObj(this.serialNumber, id.serialNumber) + && equalsObj(this.issuer, id.issuer); + } + + private boolean equalsObj(Object a, Object b) + { + return (a != null) ? a.equals(b) : b == null; + } + + public boolean match(Object obj) + { + if (obj instanceof X509CertificateHolder) + { + X509CertificateHolder certHldr = (X509CertificateHolder)obj; + + if (this.getSerialNumber() != null) + { + IssuerAndSerialNumber iAndS = new IssuerAndSerialNumber(certHldr.toASN1Structure()); + + return iAndS.getName().equals(this.issuer) + && iAndS.getSerialNumber().getValue().equals(this.serialNumber); + } + else if (subjectKeyId != null) + { + Extension ext = certHldr.getExtension(Extension.subjectKeyIdentifier); + + if (ext == null) + { + return Arrays.areEqual(subjectKeyId, MSOutlookKeyIdCalculator.calculateKeyId(certHldr.getSubjectPublicKeyInfo())); + } + + byte[] subKeyID = ASN1OctetString.getInstance(ext.getParsedValue()).getOctets(); + + return Arrays.areEqual(subjectKeyId, subKeyID); + } + } + else if (obj instanceof byte[]) + { + return Arrays.areEqual(subjectKeyId, (byte[])obj); + } + + return false; + } + + public Object clone() + { + return new X509CertificateHolderSelector(this.issuer, this.serialNumber, this.subjectKeyId); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/selector/jcajce/JcaSelectorConverter.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/selector/jcajce/JcaSelectorConverter.java new file mode 100644 index 000000000..551000b07 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/selector/jcajce/JcaSelectorConverter.java @@ -0,0 +1,35 @@ +package org.spongycastle.cert.selector.jcajce; + +import java.io.IOException; +import java.security.cert.X509CertSelector; + +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.cert.selector.X509CertificateHolderSelector; + +public class JcaSelectorConverter +{ + public JcaSelectorConverter() + { + + } + + public X509CertificateHolderSelector getCertificateHolderSelector(X509CertSelector certSelector) + { + try + { + if (certSelector.getSubjectKeyIdentifier() != null) + { + return new X509CertificateHolderSelector(X500Name.getInstance(certSelector.getIssuerAsBytes()), certSelector.getSerialNumber(), ASN1OctetString.getInstance(certSelector.getSubjectKeyIdentifier()).getOctets()); + } + else + { + return new X509CertificateHolderSelector(X500Name.getInstance(certSelector.getIssuerAsBytes()), certSelector.getSerialNumber()); + } + } + catch (IOException e) + { + throw new IllegalArgumentException("unable to convert issuer: " + e.getMessage()); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/selector/jcajce/JcaX509CertSelectorConverter.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/selector/jcajce/JcaX509CertSelectorConverter.java new file mode 100644 index 000000000..6dbcef43f --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/selector/jcajce/JcaX509CertSelectorConverter.java @@ -0,0 +1,57 @@ +package org.spongycastle.cert.selector.jcajce; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.cert.X509CertSelector; + +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.cert.selector.X509CertificateHolderSelector; + +public class JcaX509CertSelectorConverter +{ + public JcaX509CertSelectorConverter() + { + } + + protected X509CertSelector doConversion(X500Name issuer, BigInteger serialNumber, byte[] subjectKeyIdentifier) + { + X509CertSelector selector = new X509CertSelector(); + + if (issuer != null) + { + try + { + selector.setIssuer(issuer.getEncoded()); + } + catch (IOException e) + { + throw new IllegalArgumentException("unable to convert issuer: " + e.getMessage()); + } + } + + if (serialNumber != null) + { + selector.setSerialNumber(serialNumber); + } + + if (subjectKeyIdentifier != null) + { + try + { + selector.setSubjectKeyIdentifier(new DEROctetString(subjectKeyIdentifier).getEncoded()); + } + catch (IOException e) + { + throw new IllegalArgumentException("unable to convert issuer: " + e.getMessage()); + } + } + + return selector; + } + + public X509CertSelector getCertSelector(X509CertificateHolderSelector holderSelector) + { + return doConversion(holderSelector.getIssuer(), holderSelector.getSerialNumber(), holderSelector.getSubjectKeyIdentifier()); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/selector/jcajce/JcaX509CertificateHolderSelector.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/selector/jcajce/JcaX509CertificateHolderSelector.java new file mode 100644 index 000000000..fd73a9fa0 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cert/selector/jcajce/JcaX509CertificateHolderSelector.java @@ -0,0 +1,72 @@ +package org.spongycastle.cert.selector.jcajce; + +import java.math.BigInteger; +import java.security.cert.X509Certificate; + +import javax.security.auth.x500.X500Principal; + +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x509.Extension; +import org.spongycastle.cert.selector.X509CertificateHolderSelector; + +public class JcaX509CertificateHolderSelector + extends X509CertificateHolderSelector +{ + /** + * Construct a signer identifier based on the issuer, serial number and subject key identifier (if present) of the passed in + * certificate. + * + * @param certificate certificate providing the issue and serial number and subject key identifier. + */ + public JcaX509CertificateHolderSelector(X509Certificate certificate) + { + super(convertPrincipal(certificate.getIssuerX500Principal()), certificate.getSerialNumber(), getSubjectKeyId(certificate)); + } + + /** + * Construct a signer identifier based on the provided issuer and serial number.. + * + * @param issuer the issuer to use. + * @param serialNumber the serial number to use. + */ + public JcaX509CertificateHolderSelector(X500Principal issuer, BigInteger serialNumber) + { + super(convertPrincipal(issuer), serialNumber); + } + + /** + * Construct a signer identifier based on the provided issuer, serial number, and subjectKeyId.. + * + * @param issuer the issuer to use. + * @param serialNumber the serial number to use. + * @param subjectKeyId the subject key ID to use. + */ + public JcaX509CertificateHolderSelector(X500Principal issuer, BigInteger serialNumber, byte[] subjectKeyId) + { + super(convertPrincipal(issuer), serialNumber, subjectKeyId); + } + + private static X500Name convertPrincipal(X500Principal issuer) + { + if (issuer == null) + { + return null; + } + return X500Name.getInstance(issuer.getEncoded()); + } + + private static byte[] getSubjectKeyId(X509Certificate cert) + { + byte[] ext = cert.getExtensionValue(Extension.subjectKeyIdentifier.getId()); + + if (ext != null) + { + return ASN1OctetString.getInstance(ASN1OctetString.getInstance(ext).getOctets()).getOctets(); + } + else + { + return null; + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/AuthAttributesProvider.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/AuthAttributesProvider.java new file mode 100644 index 000000000..d2f14c5b4 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/AuthAttributesProvider.java @@ -0,0 +1,8 @@ +package org.spongycastle.cms; + +import org.spongycastle.asn1.ASN1Set; + +interface AuthAttributesProvider +{ + ASN1Set getAuthAttributes(); +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAbsentContent.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAbsentContent.java new file mode 100644 index 000000000..59409ea12 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAbsentContent.java @@ -0,0 +1,49 @@ +package org.spongycastle.cms; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.cms.CMSObjectIdentifiers; + +/** + * a class representing null or absent content. + */ +public class CMSAbsentContent + implements CMSTypedData, CMSReadable +{ + private final ASN1ObjectIdentifier type; + + public CMSAbsentContent() + { + this(new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId())); + } + + public CMSAbsentContent( + ASN1ObjectIdentifier type) + { + this.type = type; + } + + public InputStream getInputStream() + { + return null; + } + + public void write(OutputStream zOut) + throws IOException, CMSException + { + // do nothing + } + + public Object getContent() + { + return null; + } + + public ASN1ObjectIdentifier getContentType() + { + return type; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAlgorithm.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAlgorithm.java new file mode 100644 index 000000000..aa04a47da --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAlgorithm.java @@ -0,0 +1,51 @@ +package org.spongycastle.cms; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.cryptopro.CryptoProObjectIdentifiers; +import org.spongycastle.asn1.kisa.KISAObjectIdentifiers; +import org.spongycastle.asn1.nist.NISTObjectIdentifiers; +import org.spongycastle.asn1.ntt.NTTObjectIdentifiers; +import org.spongycastle.asn1.oiw.OIWObjectIdentifiers; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.teletrust.TeleTrusTObjectIdentifiers; +import org.spongycastle.asn1.x9.X9ObjectIdentifiers; + +public class CMSAlgorithm +{ + public static final ASN1ObjectIdentifier DES_CBC = OIWObjectIdentifiers.desCBC; + public static final ASN1ObjectIdentifier DES_EDE3_CBC = PKCSObjectIdentifiers.des_EDE3_CBC; + public static final ASN1ObjectIdentifier RC2_CBC = PKCSObjectIdentifiers.RC2_CBC; + public static final ASN1ObjectIdentifier IDEA_CBC = new ASN1ObjectIdentifier("1.3.6.1.4.1.188.7.1.1.2"); + public static final ASN1ObjectIdentifier CAST5_CBC = new ASN1ObjectIdentifier("1.2.840.113533.7.66.10"); + public static final ASN1ObjectIdentifier AES128_CBC = NISTObjectIdentifiers.id_aes128_CBC; + public static final ASN1ObjectIdentifier AES192_CBC = NISTObjectIdentifiers.id_aes192_CBC; + public static final ASN1ObjectIdentifier AES256_CBC = NISTObjectIdentifiers.id_aes256_CBC; + public static final ASN1ObjectIdentifier CAMELLIA128_CBC = NTTObjectIdentifiers.id_camellia128_cbc; + public static final ASN1ObjectIdentifier CAMELLIA192_CBC = NTTObjectIdentifiers.id_camellia192_cbc; + public static final ASN1ObjectIdentifier CAMELLIA256_CBC = NTTObjectIdentifiers.id_camellia256_cbc; + public static final ASN1ObjectIdentifier SEED_CBC = KISAObjectIdentifiers.id_seedCBC; + + public static final ASN1ObjectIdentifier DES_EDE3_WRAP = PKCSObjectIdentifiers.id_alg_CMS3DESwrap; + public static final ASN1ObjectIdentifier AES128_WRAP = NISTObjectIdentifiers.id_aes128_wrap; + public static final ASN1ObjectIdentifier AES192_WRAP = NISTObjectIdentifiers.id_aes192_wrap; + public static final ASN1ObjectIdentifier AES256_WRAP = NISTObjectIdentifiers.id_aes256_wrap; + public static final ASN1ObjectIdentifier CAMELLIA128_WRAP = NTTObjectIdentifiers.id_camellia128_wrap; + public static final ASN1ObjectIdentifier CAMELLIA192_WRAP = NTTObjectIdentifiers.id_camellia192_wrap; + public static final ASN1ObjectIdentifier CAMELLIA256_WRAP = NTTObjectIdentifiers.id_camellia256_wrap; + public static final ASN1ObjectIdentifier SEED_WRAP = KISAObjectIdentifiers.id_npki_app_cmsSeed_wrap; + + public static final ASN1ObjectIdentifier ECDH_SHA1KDF = X9ObjectIdentifiers.dhSinglePass_stdDH_sha1kdf_scheme; + public static final ASN1ObjectIdentifier ECMQV_SHA1KDF = X9ObjectIdentifiers.mqvSinglePass_sha1kdf_scheme; + + public static final ASN1ObjectIdentifier SHA1 = OIWObjectIdentifiers.idSHA1; + public static final ASN1ObjectIdentifier SHA224 = NISTObjectIdentifiers.id_sha224; + public static final ASN1ObjectIdentifier SHA256 = NISTObjectIdentifiers.id_sha256; + public static final ASN1ObjectIdentifier SHA384 = NISTObjectIdentifiers.id_sha384; + public static final ASN1ObjectIdentifier SHA512 = NISTObjectIdentifiers.id_sha512; + public static final ASN1ObjectIdentifier MD5 = PKCSObjectIdentifiers.md5; + public static final ASN1ObjectIdentifier GOST3411 = CryptoProObjectIdentifiers.gostR3411; + public static final ASN1ObjectIdentifier RIPEMD128 = TeleTrusTObjectIdentifiers.ripemd128; + public static final ASN1ObjectIdentifier RIPEMD160 = TeleTrusTObjectIdentifiers.ripemd160; + public static final ASN1ObjectIdentifier RIPEMD256 = TeleTrusTObjectIdentifiers.ripemd256; + +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAttributeTableGenerationException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAttributeTableGenerationException.java new file mode 100644 index 000000000..e328f8e59 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAttributeTableGenerationException.java @@ -0,0 +1,32 @@ +package org.spongycastle.cms; + +public class CMSAttributeTableGenerationException + extends CMSRuntimeException +{ + Exception e; + + public CMSAttributeTableGenerationException( + String name) + { + super(name); + } + + public CMSAttributeTableGenerationException( + String name, + Exception e) + { + super(name); + + this.e = e; + } + + public Exception getUnderlyingException() + { + return e; + } + + public Throwable getCause() + { + return e; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAttributeTableGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAttributeTableGenerator.java new file mode 100644 index 000000000..9c44be361 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAttributeTableGenerator.java @@ -0,0 +1,19 @@ +package org.spongycastle.cms; + +import org.spongycastle.asn1.cms.AttributeTable; + +import java.util.Map; + +/** + * Note: The SIGNATURE parameter is only available when generating unsigned attributes. + */ +public interface CMSAttributeTableGenerator +{ + static final String CONTENT_TYPE = "contentType"; + static final String DIGEST = "digest"; + static final String SIGNATURE = "encryptedDigest"; + static final String DIGEST_ALGORITHM_IDENTIFIER = "digestAlgID"; + + AttributeTable getAttributes(Map parameters) + throws CMSAttributeTableGenerationException; +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAuthEnvelopedData.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAuthEnvelopedData.java new file mode 100644 index 000000000..df3ab5cf5 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAuthEnvelopedData.java @@ -0,0 +1,78 @@ +package org.spongycastle.cms; + +import java.io.IOException; +import java.io.InputStream; + +import org.spongycastle.asn1.ASN1Set; +import org.spongycastle.asn1.cms.AuthEnvelopedData; +import org.spongycastle.asn1.cms.ContentInfo; +import org.spongycastle.asn1.cms.EncryptedContentInfo; +import org.spongycastle.asn1.cms.OriginatorInfo; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +/** + * containing class for an CMS AuthEnveloped Data object + */ +class CMSAuthEnvelopedData +{ + RecipientInformationStore recipientInfoStore; + ContentInfo contentInfo; + + private OriginatorInfo originator; + private AlgorithmIdentifier authEncAlg; + private ASN1Set authAttrs; + private byte[] mac; + private ASN1Set unauthAttrs; + + public CMSAuthEnvelopedData(byte[] authEnvData) throws CMSException + { + this(CMSUtils.readContentInfo(authEnvData)); + } + + public CMSAuthEnvelopedData(InputStream authEnvData) throws CMSException + { + this(CMSUtils.readContentInfo(authEnvData)); + } + + public CMSAuthEnvelopedData(ContentInfo contentInfo) throws CMSException + { + this.contentInfo = contentInfo; + + AuthEnvelopedData authEnvData = AuthEnvelopedData.getInstance(contentInfo.getContent()); + + this.originator = authEnvData.getOriginatorInfo(); + + // + // read the recipients + // + ASN1Set recipientInfos = authEnvData.getRecipientInfos(); + + // + // read the auth-encrypted content info + // + EncryptedContentInfo authEncInfo = authEnvData.getAuthEncryptedContentInfo(); + this.authEncAlg = authEncInfo.getContentEncryptionAlgorithm(); +// final CMSProcessable processable = new CMSProcessableByteArray( +// authEncInfo.getEncryptedContent().getOctets()); + CMSSecureReadable secureReadable = new CMSSecureReadable() + { + + public InputStream getInputStream() + throws IOException, CMSException + { + return null; + } + }; + + // + // build the RecipientInformationStore + // + this.recipientInfoStore = CMSEnvelopedHelper.buildRecipientInformationStore( + recipientInfos, this.authEncAlg, secureReadable); + + // FIXME These need to be passed to the AEAD cipher as AAD (Additional Authenticated Data) + this.authAttrs = authEnvData.getAuthAttrs(); + this.mac = authEnvData.getMac().getOctets(); + this.unauthAttrs = authEnvData.getUnauthAttrs(); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAuthEnvelopedGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAuthEnvelopedGenerator.java new file mode 100644 index 000000000..f3a802cdf --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAuthEnvelopedGenerator.java @@ -0,0 +1,13 @@ +package org.spongycastle.cms; + +import org.spongycastle.asn1.nist.NISTObjectIdentifiers; + +class CMSAuthEnvelopedGenerator +{ + public static final String AES128_CCM = NISTObjectIdentifiers.id_aes128_CCM.getId(); + public static final String AES192_CCM = NISTObjectIdentifiers.id_aes192_CCM.getId(); + public static final String AES256_CCM = NISTObjectIdentifiers.id_aes256_CCM.getId(); + public static final String AES128_GCM = NISTObjectIdentifiers.id_aes128_GCM.getId(); + public static final String AES192_GCM = NISTObjectIdentifiers.id_aes192_GCM.getId(); + public static final String AES256_GCM = NISTObjectIdentifiers.id_aes256_GCM.getId(); +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAuthenticatedData.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAuthenticatedData.java new file mode 100644 index 000000000..898d5c0c3 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAuthenticatedData.java @@ -0,0 +1,260 @@ +package org.spongycastle.cms; + +import java.io.IOException; +import java.io.InputStream; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.ASN1Set; +import org.spongycastle.asn1.cms.AttributeTable; +import org.spongycastle.asn1.cms.AuthenticatedData; +import org.spongycastle.asn1.cms.CMSAttributes; +import org.spongycastle.asn1.cms.ContentInfo; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.operator.DigestCalculatorProvider; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.util.Arrays; + +/** + * containing class for an CMS Authenticated Data object + */ +public class CMSAuthenticatedData +{ + RecipientInformationStore recipientInfoStore; + ContentInfo contentInfo; + + private AlgorithmIdentifier macAlg; + private ASN1Set authAttrs; + private ASN1Set unauthAttrs; + private byte[] mac; + private OriginatorInformation originatorInfo; + + public CMSAuthenticatedData( + byte[] authData) + throws CMSException + { + this(CMSUtils.readContentInfo(authData)); + } + + public CMSAuthenticatedData( + byte[] authData, + DigestCalculatorProvider digestCalculatorProvider) + throws CMSException + { + this(CMSUtils.readContentInfo(authData), digestCalculatorProvider); + } + + public CMSAuthenticatedData( + InputStream authData) + throws CMSException + { + this(CMSUtils.readContentInfo(authData)); + } + + public CMSAuthenticatedData( + InputStream authData, + DigestCalculatorProvider digestCalculatorProvider) + throws CMSException + { + this(CMSUtils.readContentInfo(authData), digestCalculatorProvider); + } + + public CMSAuthenticatedData( + ContentInfo contentInfo) + throws CMSException + { + this(contentInfo, null); + } + + public CMSAuthenticatedData( + ContentInfo contentInfo, + DigestCalculatorProvider digestCalculatorProvider) + throws CMSException + { + this.contentInfo = contentInfo; + + AuthenticatedData authData = AuthenticatedData.getInstance(contentInfo.getContent()); + + if (authData.getOriginatorInfo() != null) + { + this.originatorInfo = new OriginatorInformation(authData.getOriginatorInfo()); + } + + // + // read the recipients + // + ASN1Set recipientInfos = authData.getRecipientInfos(); + + this.macAlg = authData.getMacAlgorithm(); + + + this.authAttrs = authData.getAuthAttrs(); + this.mac = authData.getMac().getOctets(); + this.unauthAttrs = authData.getUnauthAttrs(); + + // + // read the authenticated content info + // + ContentInfo encInfo = authData.getEncapsulatedContentInfo(); + CMSReadable readable = new CMSProcessableByteArray( + ASN1OctetString.getInstance(encInfo.getContent()).getOctets()); + + // + // build the RecipientInformationStore + // + if (authAttrs != null) + { + if (digestCalculatorProvider == null) + { + throw new CMSException("a digest calculator provider is required if authenticated attributes are present"); + } + + try + { + CMSSecureReadable secureReadable = new CMSEnvelopedHelper.CMSDigestAuthenticatedSecureReadable(digestCalculatorProvider.get(authData.getDigestAlgorithm()), readable); + + this.recipientInfoStore = CMSEnvelopedHelper.buildRecipientInformationStore(recipientInfos, this.macAlg, secureReadable, new AuthAttributesProvider() + { + public ASN1Set getAuthAttributes() + { + return authAttrs; + } + }); + } + catch (OperatorCreationException e) + { + throw new CMSException("unable to create digest calculator: " + e.getMessage(), e); + } + } + else + { + CMSSecureReadable secureReadable = new CMSEnvelopedHelper.CMSAuthenticatedSecureReadable(this.macAlg, readable); + + this.recipientInfoStore = CMSEnvelopedHelper.buildRecipientInformationStore(recipientInfos, this.macAlg, secureReadable); + } + } + + /** + * Return the originator information associated with this message if present. + * + * @return OriginatorInformation, null if not present. + */ + public OriginatorInformation getOriginatorInfo() + { + return originatorInfo; + } + + public byte[] getMac() + { + return Arrays.clone(mac); + } + + private byte[] encodeObj( + ASN1Encodable obj) + throws IOException + { + if (obj != null) + { + return obj.toASN1Primitive().getEncoded(); + } + + return null; + } + + /** + * Return the MAC algorithm details for the MAC associated with the data in this object. + * + * @return AlgorithmIdentifier representing the MAC algorithm. + */ + public AlgorithmIdentifier getMacAlgorithm() + { + return macAlg; + } + + /** + * return the object identifier for the content MAC algorithm. + */ + public String getMacAlgOID() + { + return macAlg.getAlgorithm().getId(); + } + + /** + * return the ASN.1 encoded MAC algorithm parameters, or null if + * there aren't any. + */ + public byte[] getMacAlgParams() + { + try + { + return encodeObj(macAlg.getParameters()); + } + catch (Exception e) + { + throw new RuntimeException("exception getting encryption parameters " + e); + } + } + + /** + * return a store of the intended recipients for this message + */ + public RecipientInformationStore getRecipientInfos() + { + return recipientInfoStore; + } + + /** + * return the ContentInfo + */ + public ContentInfo getContentInfo() + { + return contentInfo; + } + + /** + * return a table of the digested attributes indexed by + * the OID of the attribute. + */ + public AttributeTable getAuthAttrs() + { + if (authAttrs == null) + { + return null; + } + + return new AttributeTable(authAttrs); + } + + /** + * return a table of the undigested attributes indexed by + * the OID of the attribute. + */ + public AttributeTable getUnauthAttrs() + { + if (unauthAttrs == null) + { + return null; + } + + return new AttributeTable(unauthAttrs); + } + + /** + * return the ASN.1 encoded representation of this object. + */ + public byte[] getEncoded() + throws IOException + { + return contentInfo.getEncoded(); + } + + public byte[] getContentDigest() + { + if (authAttrs != null) + { + return ASN1OctetString.getInstance(getAuthAttrs().get(CMSAttributes.messageDigest).getAttrValues().getObjectAt(0)).getOctets(); + } + + return null; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAuthenticatedDataGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAuthenticatedDataGenerator.java new file mode 100644 index 000000000..ca1f95a56 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAuthenticatedDataGenerator.java @@ -0,0 +1,181 @@ +package org.spongycastle.cms; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Encoding; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.ASN1Set; +import org.spongycastle.asn1.BEROctetString; +import org.spongycastle.asn1.BERSet; +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.DERSet; +import org.spongycastle.asn1.cms.AuthenticatedData; +import org.spongycastle.asn1.cms.CMSObjectIdentifiers; +import org.spongycastle.asn1.cms.ContentInfo; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.operator.DigestCalculator; +import org.spongycastle.operator.DigestCalculatorProvider; +import org.spongycastle.operator.MacCalculator; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.util.io.TeeOutputStream; + +/** + * General class for generating a CMS authenticated-data message. + * + * A simple example of usage. + * + * <pre> + * CMSAuthenticatedDataGenerator fact = new CMSAuthenticatedDataGenerator(); + * + * adGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(recipientCert).setProvider("SC")); + * + * CMSAuthenticatedData data = fact.generate(new CMSProcessableByteArray(data), + * new JceCMSMacCalculatorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider(BC).build())); + * </pre> + */ +public class CMSAuthenticatedDataGenerator + extends CMSAuthenticatedGenerator +{ + /** + * base constructor + */ + public CMSAuthenticatedDataGenerator() + { + } + + /** + * Generate an authenticated data object from the passed in typedData and MacCalculator. + * + * @param typedData the data to have a MAC attached. + * @param macCalculator the calculator of the MAC to be attached. + * @return the resulting CMSAuthenticatedData object. + * @throws CMSException on failure in encoding data or processing recipients. + */ + public CMSAuthenticatedData generate(CMSTypedData typedData, MacCalculator macCalculator) + throws CMSException + { + return generate(typedData, macCalculator, null); + } + + /** + * Generate an authenticated data object from the passed in typedData and MacCalculator. + * + * @param typedData the data to have a MAC attached. + * @param macCalculator the calculator of the MAC to be attached. + * @param digestCalculator calculator for computing digest of the encapsulated data. + * @return the resulting CMSAuthenticatedData object. + * @throws CMSException on failure in encoding data or processing recipients. + */ + public CMSAuthenticatedData generate(CMSTypedData typedData, MacCalculator macCalculator, final DigestCalculator digestCalculator) + throws CMSException + { + ASN1EncodableVector recipientInfos = new ASN1EncodableVector(); + ASN1OctetString encContent; + ASN1OctetString macResult; + + for (Iterator it = recipientInfoGenerators.iterator(); it.hasNext();) + { + RecipientInfoGenerator recipient = (RecipientInfoGenerator)it.next(); + + recipientInfos.add(recipient.generate(macCalculator.getKey())); + } + + AuthenticatedData authData; + + if (digestCalculator != null) + { + try + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + OutputStream out = new TeeOutputStream(digestCalculator.getOutputStream(), bOut); + + typedData.write(out); + + out.close(); + + encContent = new BEROctetString(bOut.toByteArray()); + } + catch (IOException e) + { + throw new CMSException("unable to perform digest calculation: " + e.getMessage(), e); + } + + Map parameters = getBaseParameters(typedData.getContentType(), digestCalculator.getAlgorithmIdentifier(), digestCalculator.getDigest()); + + if (authGen == null) + { + authGen = new DefaultAuthenticatedAttributeTableGenerator(); + } + ASN1Set authed = new DERSet(authGen.getAttributes(Collections.unmodifiableMap(parameters)).toASN1EncodableVector()); + + try + { + OutputStream mOut = macCalculator.getOutputStream(); + + mOut.write(authed.getEncoded(ASN1Encoding.DER)); + + mOut.close(); + + macResult = new DEROctetString(macCalculator.getMac()); + } + catch (IOException e) + { + throw new CMSException("exception decoding algorithm parameters.", e); + } + ASN1Set unauthed = (unauthGen != null) ? new BERSet(unauthGen.getAttributes(Collections.unmodifiableMap(parameters)).toASN1EncodableVector()) : null; + + ContentInfo eci = new ContentInfo( + CMSObjectIdentifiers.data, + encContent); + + authData = new AuthenticatedData(originatorInfo, new DERSet(recipientInfos), macCalculator.getAlgorithmIdentifier(), digestCalculator.getAlgorithmIdentifier(), eci, authed, macResult, unauthed); + } + else + { + try + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + OutputStream mOut = new TeeOutputStream(bOut, macCalculator.getOutputStream()); + + typedData.write(mOut); + + mOut.close(); + + encContent = new BEROctetString(bOut.toByteArray()); + + macResult = new DEROctetString(macCalculator.getMac()); + } + catch (IOException e) + { + throw new CMSException("exception decoding algorithm parameters.", e); + } + + ASN1Set unauthed = (unauthGen != null) ? new BERSet(unauthGen.getAttributes(new HashMap()).toASN1EncodableVector()) : null; + + ContentInfo eci = new ContentInfo( + CMSObjectIdentifiers.data, + encContent); + + authData = new AuthenticatedData(originatorInfo, new DERSet(recipientInfos), macCalculator.getAlgorithmIdentifier(), null, eci, null, macResult, unauthed); + } + + ContentInfo contentInfo = new ContentInfo( + CMSObjectIdentifiers.authenticatedData, authData); + + return new CMSAuthenticatedData(contentInfo, new DigestCalculatorProvider() + { + public DigestCalculator get(AlgorithmIdentifier digestAlgorithmIdentifier) + throws OperatorCreationException + { + return digestCalculator; + } + }); + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAuthenticatedDataParser.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAuthenticatedDataParser.java new file mode 100644 index 000000000..b524b4ac9 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAuthenticatedDataParser.java @@ -0,0 +1,348 @@ +package org.spongycastle.cms; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.ASN1OctetStringParser; +import org.spongycastle.asn1.ASN1SequenceParser; +import org.spongycastle.asn1.ASN1Set; +import org.spongycastle.asn1.ASN1SetParser; +import org.spongycastle.asn1.BERTags; +import org.spongycastle.asn1.DERSet; +import org.spongycastle.asn1.cms.AttributeTable; +import org.spongycastle.asn1.cms.AuthenticatedDataParser; +import org.spongycastle.asn1.cms.CMSAttributes; +import org.spongycastle.asn1.cms.ContentInfoParser; +import org.spongycastle.asn1.cms.OriginatorInfo; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.operator.DigestCalculatorProvider; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.util.Arrays; + +/** + * Parsing class for an CMS Authenticated Data object from an input stream. + * <p> + * Note: that because we are in a streaming mode only one recipient can be tried and it is important + * that the methods on the parser are called in the appropriate order. + * </p> + * <p> + * Example of use - assuming the first recipient matches the private key we have. + * <pre> + * CMSAuthenticatedDataParser ad = new CMSAuthenticatedDataParser(inputStream); + * + * RecipientInformationStore recipients = ad.getRecipientInfos(); + * + * Collection c = recipients.getRecipients(); + * Iterator it = c.iterator(); + * + * if (it.hasNext()) + * { + * RecipientInformation recipient = (RecipientInformation)it.next(); + * + * CMSTypedStream recData = recipient.getContentStream(new JceKeyTransAuthenticatedRecipient(privateKey).setProvider("SC")); + * + * processDataStream(recData.getContentStream()); + * + * if (!Arrays.equals(ad.getMac(), recipient.getMac()) + * { + * System.err.println("Data corrupted!!!!"); + * } + * } + * </pre> + * Note: this class does not introduce buffering - if you are processing large files you should create + * the parser with: + * <pre> + * CMSAuthenticatedDataParser ep = new CMSAuthenticatedDataParser(new BufferedInputStream(inputStream, bufSize)); + * </pre> + * where bufSize is a suitably large buffer size. + */ +public class CMSAuthenticatedDataParser + extends CMSContentInfoParser +{ + RecipientInformationStore recipientInfoStore; + AuthenticatedDataParser authData; + + private AlgorithmIdentifier macAlg; + private byte[] mac; + private AttributeTable authAttrs; + private ASN1Set authAttrSet; + private AttributeTable unauthAttrs; + + private boolean authAttrNotRead; + private boolean unauthAttrNotRead; + private OriginatorInformation originatorInfo; + + public CMSAuthenticatedDataParser( + byte[] envelopedData) + throws CMSException, IOException + { + this(new ByteArrayInputStream(envelopedData)); + } + + public CMSAuthenticatedDataParser( + byte[] envelopedData, + DigestCalculatorProvider digestCalculatorProvider) + throws CMSException, IOException + { + this(new ByteArrayInputStream(envelopedData), digestCalculatorProvider); + } + + public CMSAuthenticatedDataParser( + InputStream envelopedData) + throws CMSException, IOException + { + this(envelopedData, null); + } + + public CMSAuthenticatedDataParser( + InputStream envelopedData, + DigestCalculatorProvider digestCalculatorProvider) + throws CMSException, IOException + { + super(envelopedData); + + this.authAttrNotRead = true; + this.authData = new AuthenticatedDataParser((ASN1SequenceParser)_contentInfo.getContent(BERTags.SEQUENCE)); + + // TODO Validate version? + //DERInteger version = this.authData.getVersion(); + + OriginatorInfo info = authData.getOriginatorInfo(); + + if (info != null) + { + this.originatorInfo = new OriginatorInformation(info); + } + // + // read the recipients + // + ASN1Set recipientInfos = ASN1Set.getInstance(authData.getRecipientInfos().toASN1Primitive()); + + this.macAlg = authData.getMacAlgorithm(); + + // + // build the RecipientInformationStore + // + AlgorithmIdentifier digestAlgorithm = authData.getDigestAlgorithm(); + + if (digestAlgorithm != null) + { + if (digestCalculatorProvider == null) + { + throw new CMSException("a digest calculator provider is required if authenticated attributes are present"); + } + + // + // read the authenticated content info + // + ContentInfoParser data = authData.getEncapsulatedContentInfo(); + CMSReadable readable = new CMSProcessableInputStream( + ((ASN1OctetStringParser)data.getContent(BERTags.OCTET_STRING)).getOctetStream()); + + try + { + CMSSecureReadable secureReadable = new CMSEnvelopedHelper.CMSDigestAuthenticatedSecureReadable(digestCalculatorProvider.get(digestAlgorithm), readable); + + this.recipientInfoStore = CMSEnvelopedHelper.buildRecipientInformationStore(recipientInfos, this.macAlg, secureReadable, new AuthAttributesProvider() + { + public ASN1Set getAuthAttributes() + { + try + { + return getAuthAttrSet(); + } + catch (IOException e) + { + throw new IllegalStateException("can't parse authenticated attributes!"); + } + } + }); + } + catch (OperatorCreationException e) + { + throw new CMSException("unable to create digest calculator: " + e.getMessage(), e); + } + } + else + { + // + // read the authenticated content info + // + ContentInfoParser data = authData.getEncapsulatedContentInfo(); + CMSReadable readable = new CMSProcessableInputStream( + ((ASN1OctetStringParser)data.getContent(BERTags.OCTET_STRING)).getOctetStream()); + + CMSSecureReadable secureReadable = new CMSEnvelopedHelper.CMSAuthenticatedSecureReadable(this.macAlg, readable); + + this.recipientInfoStore = CMSEnvelopedHelper.buildRecipientInformationStore(recipientInfos, this.macAlg, secureReadable); + } + + + } + + /** + * Return the originator information associated with this message if present. + * + * @return OriginatorInformation, null if not present. + */ + public OriginatorInformation getOriginatorInfo() + { + return originatorInfo; + } + + /** + * Return the MAC algorithm details for the MAC associated with the data in this object. + * + * @return AlgorithmIdentifier representing the MAC algorithm. + */ + public AlgorithmIdentifier getMacAlgorithm() + { + return macAlg; + } + + /** + * return the object identifier for the mac algorithm. + */ + public String getMacAlgOID() + { + return macAlg.getAlgorithm().toString(); + } + + /** + * return the ASN.1 encoded encryption algorithm parameters, or null if + * there aren't any. + */ + public byte[] getMacAlgParams() + { + try + { + return encodeObj(macAlg.getParameters()); + } + catch (Exception e) + { + throw new RuntimeException("exception getting encryption parameters " + e); + } + } + + /** + * return a store of the intended recipients for this message + */ + public RecipientInformationStore getRecipientInfos() + { + return recipientInfoStore; + } + + public byte[] getMac() + throws IOException + { + if (mac == null) + { + getAuthAttrs(); + mac = authData.getMac().getOctets(); + } + return Arrays.clone(mac); + } + + private ASN1Set getAuthAttrSet() + throws IOException + { + if (authAttrs == null && authAttrNotRead) + { + ASN1SetParser set = authData.getAuthAttrs(); + + if (set != null) + { + authAttrSet = (ASN1Set)set.toASN1Primitive(); + } + + authAttrNotRead = false; + } + + return authAttrSet; + } + + /** + * return a table of the unauthenticated attributes indexed by + * the OID of the attribute. + * @exception java.io.IOException + */ + public AttributeTable getAuthAttrs() + throws IOException + { + if (authAttrs == null && authAttrNotRead) + { + ASN1Set set = getAuthAttrSet(); + + if (set != null) + { + authAttrs = new AttributeTable(set); + } + } + + return authAttrs; + } + + /** + * return a table of the unauthenticated attributes indexed by + * the OID of the attribute. + * @exception java.io.IOException + */ + public AttributeTable getUnauthAttrs() + throws IOException + { + if (unauthAttrs == null && unauthAttrNotRead) + { + ASN1SetParser set = authData.getUnauthAttrs(); + + unauthAttrNotRead = false; + + if (set != null) + { + ASN1EncodableVector v = new ASN1EncodableVector(); + ASN1Encodable o; + + while ((o = set.readObject()) != null) + { + ASN1SequenceParser seq = (ASN1SequenceParser)o; + + v.add(seq.toASN1Primitive()); + } + + unauthAttrs = new AttributeTable(new DERSet(v)); + } + } + + return unauthAttrs; + } + + private byte[] encodeObj( + ASN1Encodable obj) + throws IOException + { + if (obj != null) + { + return obj.toASN1Primitive().getEncoded(); + } + + return null; + } + + /** + * This will only be valid after the content has been read. + * + * @return the contents of the messageDigest attribute, if available. Null if not present. + */ + public byte[] getContentDigest() + { + if (authAttrs != null) + { + return ASN1OctetString.getInstance(authAttrs.get(CMSAttributes.messageDigest).getAttrValues().getObjectAt(0)).getOctets(); + } + + return null; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAuthenticatedDataStreamGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAuthenticatedDataStreamGenerator.java new file mode 100644 index 000000000..739b0b1d5 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAuthenticatedDataStreamGenerator.java @@ -0,0 +1,310 @@ +package org.spongycastle.cms; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Encoding; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1Set; +import org.spongycastle.asn1.BERSequenceGenerator; +import org.spongycastle.asn1.BERSet; +import org.spongycastle.asn1.DERInteger; +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.DERSet; +import org.spongycastle.asn1.DERTaggedObject; +import org.spongycastle.asn1.cms.AuthenticatedData; +import org.spongycastle.asn1.cms.CMSObjectIdentifiers; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.operator.DigestCalculator; +import org.spongycastle.operator.MacCalculator; +import org.spongycastle.util.io.TeeOutputStream; + +/** + * General class for generating a CMS authenticated-data message stream. + * <p> + * A simple example of usage. + * <pre> + * CMSAuthenticatedDataStreamGenerator edGen = new CMSAuthenticatedDataStreamGenerator(); + * + * edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(cert).setProvider("SC")); + * + * ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + * + * OutputStream out = edGen.open( + * bOut, new JceCMSMacCalculatorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider("SC").build());* + * out.write(data); + * + * out.close(); + * </pre> + */ +public class CMSAuthenticatedDataStreamGenerator + extends CMSAuthenticatedGenerator +{ + // Currently not handled +// private Object _originatorInfo = null; +// private Object _unprotectedAttributes = null; + private int bufferSize; + private boolean berEncodeRecipientSet; + private MacCalculator macCalculator; + + /** + * base constructor + */ + public CMSAuthenticatedDataStreamGenerator() + { + } + + /** + * Set the underlying string size for encapsulated data + * + * @param bufferSize length of octet strings to buffer the data. + */ + public void setBufferSize( + int bufferSize) + { + this.bufferSize = bufferSize; + } + + /** + * Use a BER Set to store the recipient information. By default recipients are + * stored in a DER encoding. + * + * @param useBerEncodingForRecipients true if a BER set should be used, false if DER. + */ + public void setBEREncodeRecipients( + boolean useBerEncodingForRecipients) + { + berEncodeRecipientSet = useBerEncodingForRecipients; + } + + /** + * generate an authenticated data structure with the encapsulated bytes marked as DATA. + * + * @param out the stream to store the authenticated structure in. + * @param macCalculator calculator for the MAC to be attached to the data. + */ + public OutputStream open( + OutputStream out, + MacCalculator macCalculator) + throws CMSException + { + return open(CMSObjectIdentifiers.data, out, macCalculator); + } + + public OutputStream open( + OutputStream out, + MacCalculator macCalculator, + DigestCalculator digestCalculator) + throws CMSException + { + return open(CMSObjectIdentifiers.data, out, macCalculator, digestCalculator); + } + + /** + * generate an authenticated data structure with the encapsulated bytes marked as type dataType. + * + * @param dataType the type of the data been written to the object. + * @param out the stream to store the authenticated structure in. + * @param macCalculator calculator for the MAC to be attached to the data. + */ + public OutputStream open( + ASN1ObjectIdentifier dataType, + OutputStream out, + MacCalculator macCalculator) + throws CMSException + { + return open(dataType, out, macCalculator, null); + } + + /** + * generate an authenticated data structure with the encapsulated bytes marked as type dataType. + * + * @param dataType the type of the data been written to the object. + * @param out the stream to store the authenticated structure in. + * @param macCalculator calculator for the MAC to be attached to the data. + * @param digestCalculator calculator for computing digest of the encapsulated data. + */ + public OutputStream open( + ASN1ObjectIdentifier dataType, + OutputStream out, + MacCalculator macCalculator, + DigestCalculator digestCalculator) + throws CMSException + { + this.macCalculator = macCalculator; + + try + { + ASN1EncodableVector recipientInfos = new ASN1EncodableVector(); + + for (Iterator it = recipientInfoGenerators.iterator(); it.hasNext();) + { + RecipientInfoGenerator recipient = (RecipientInfoGenerator)it.next(); + + recipientInfos.add(recipient.generate(macCalculator.getKey())); + } + + // + // ContentInfo + // + BERSequenceGenerator cGen = new BERSequenceGenerator(out); + + cGen.addObject(CMSObjectIdentifiers.authenticatedData); + + // + // Authenticated Data + // + BERSequenceGenerator authGen = new BERSequenceGenerator(cGen.getRawOutputStream(), 0, true); + + authGen.addObject(new DERInteger(AuthenticatedData.calculateVersion(originatorInfo))); + + if (originatorInfo != null) + { + authGen.addObject(new DERTaggedObject(false, 0, originatorInfo)); + } + + if (berEncodeRecipientSet) + { + authGen.getRawOutputStream().write(new BERSet(recipientInfos).getEncoded()); + } + else + { + authGen.getRawOutputStream().write(new DERSet(recipientInfos).getEncoded()); + } + + AlgorithmIdentifier macAlgId = macCalculator.getAlgorithmIdentifier(); + + authGen.getRawOutputStream().write(macAlgId.getEncoded()); + + if (digestCalculator != null) + { + authGen.addObject(new DERTaggedObject(false, 1, digestCalculator.getAlgorithmIdentifier())); + } + + BERSequenceGenerator eiGen = new BERSequenceGenerator(authGen.getRawOutputStream()); + + eiGen.addObject(dataType); + + OutputStream octetStream = CMSUtils.createBEROctetOutputStream( + eiGen.getRawOutputStream(), 0, false, bufferSize); + + OutputStream mOut; + + if (digestCalculator != null) + { + mOut = new TeeOutputStream(octetStream, digestCalculator.getOutputStream()); + } + else + { + mOut = new TeeOutputStream(octetStream, macCalculator.getOutputStream()); + } + + return new CmsAuthenticatedDataOutputStream(macCalculator, digestCalculator, dataType, mOut, cGen, authGen, eiGen); + } + catch (IOException e) + { + throw new CMSException("exception decoding algorithm parameters.", e); + } + } + + private class CmsAuthenticatedDataOutputStream + extends OutputStream + { + private OutputStream dataStream; + private BERSequenceGenerator cGen; + private BERSequenceGenerator envGen; + private BERSequenceGenerator eiGen; + private MacCalculator macCalculator; + private DigestCalculator digestCalculator; + private ASN1ObjectIdentifier contentType; + + public CmsAuthenticatedDataOutputStream( + MacCalculator macCalculator, + DigestCalculator digestCalculator, + ASN1ObjectIdentifier contentType, + OutputStream dataStream, + BERSequenceGenerator cGen, + BERSequenceGenerator envGen, + BERSequenceGenerator eiGen) + { + this.macCalculator = macCalculator; + this.digestCalculator = digestCalculator; + this.contentType = contentType; + this.dataStream = dataStream; + this.cGen = cGen; + this.envGen = envGen; + this.eiGen = eiGen; + } + + public void write( + int b) + throws IOException + { + dataStream.write(b); + } + + public void write( + byte[] bytes, + int off, + int len) + throws IOException + { + dataStream.write(bytes, off, len); + } + + public void write( + byte[] bytes) + throws IOException + { + dataStream.write(bytes); + } + + public void close() + throws IOException + { + dataStream.close(); + eiGen.close(); + + Map parameters; + + if (digestCalculator != null) + { + parameters = Collections.unmodifiableMap(getBaseParameters(contentType, digestCalculator.getAlgorithmIdentifier(), digestCalculator.getDigest())); + + if (authGen == null) + { + authGen = new DefaultAuthenticatedAttributeTableGenerator(); + } + + ASN1Set authed = new DERSet(authGen.getAttributes(parameters).toASN1EncodableVector()); + + OutputStream mOut = macCalculator.getOutputStream(); + + mOut.write(authed.getEncoded(ASN1Encoding.DER)); + + mOut.close(); + + envGen.addObject(new DERTaggedObject(false, 2, authed)); + } + else + { + parameters = Collections.unmodifiableMap(new HashMap()); + } + + envGen.addObject(new DEROctetString(macCalculator.getMac())); + + if (unauthGen != null) + { + envGen.addObject(new DERTaggedObject(false, 3, new BERSet(unauthGen.getAttributes(parameters).toASN1EncodableVector()))); + } + + envGen.close(); + cGen.close(); + } + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAuthenticatedGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAuthenticatedGenerator.java new file mode 100644 index 000000000..d5ea1b8cf --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSAuthenticatedGenerator.java @@ -0,0 +1,40 @@ +package org.spongycastle.cms; + +import java.util.HashMap; +import java.util.Map; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +public class CMSAuthenticatedGenerator + extends CMSEnvelopedGenerator +{ + protected CMSAttributeTableGenerator authGen; + protected CMSAttributeTableGenerator unauthGen; + + /** + * base constructor + */ + public CMSAuthenticatedGenerator() + { + } + + public void setAuthenticatedAttributeGenerator(CMSAttributeTableGenerator authGen) + { + this.authGen = authGen; + } + + public void setUnauthenticatedAttributeGenerator(CMSAttributeTableGenerator unauthGen) + { + this.unauthGen = unauthGen; + } + + protected Map getBaseParameters(ASN1ObjectIdentifier contentType, AlgorithmIdentifier digAlgId, byte[] hash) + { + Map param = new HashMap(); + param.put(CMSAttributeTableGenerator.CONTENT_TYPE, contentType); + param.put(CMSAttributeTableGenerator.DIGEST_ALGORITHM_IDENTIFIER, digAlgId); + param.put(CMSAttributeTableGenerator.DIGEST, hash.clone()); + return param; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSCompressedData.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSCompressedData.java new file mode 100644 index 000000000..f6aa02d10 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSCompressedData.java @@ -0,0 +1,107 @@ +package org.spongycastle.cms; + +import java.io.IOException; +import java.io.InputStream; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.cms.CompressedData; +import org.spongycastle.asn1.cms.ContentInfo; +import org.spongycastle.operator.InputExpander; +import org.spongycastle.operator.InputExpanderProvider; + +/** + * containing class for an CMS Compressed Data object + * <pre> + * CMSCompressedData cd = new CMSCompressedData(inputStream); + * + * process(cd.getContent(new ZlibExpanderProvider())); + * </pre> + */ +public class CMSCompressedData +{ + ContentInfo contentInfo; + CompressedData comData; + + public CMSCompressedData( + byte[] compressedData) + throws CMSException + { + this(CMSUtils.readContentInfo(compressedData)); + } + + public CMSCompressedData( + InputStream compressedData) + throws CMSException + { + this(CMSUtils.readContentInfo(compressedData)); + } + + public CMSCompressedData( + ContentInfo contentInfo) + throws CMSException + { + this.contentInfo = contentInfo; + + try + { + this.comData = CompressedData.getInstance(contentInfo.getContent()); + } + catch (ClassCastException e) + { + throw new CMSException("Malformed content.", e); + } + catch (IllegalArgumentException e) + { + throw new CMSException("Malformed content.", e); + } + } + + public ASN1ObjectIdentifier getContentType() + { + return contentInfo.getContentType(); + } + + /** + * Return the uncompressed content. + * + * @param expanderProvider a provider of expander algorithm implementations. + * @return the uncompressed content + * @throws CMSException if there is an exception un-compressing the data. + */ + public byte[] getContent(InputExpanderProvider expanderProvider) + throws CMSException + { + ContentInfo content = comData.getEncapContentInfo(); + + ASN1OctetString bytes = (ASN1OctetString)content.getContent(); + InputExpander expander = expanderProvider.get(comData.getCompressionAlgorithmIdentifier()); + InputStream zIn = expander.getInputStream(bytes.getOctetStream()); + + try + { + return CMSUtils.streamToByteArray(zIn); + } + catch (IOException e) + { + throw new CMSException("exception reading compressed stream.", e); + } + } + + /** + * return the ContentInfo + */ + public ContentInfo toASN1Structure() + { + return contentInfo; + } + + /** + * return the ASN.1 encoded representation of this object. + */ + public byte[] getEncoded() + throws IOException + { + return contentInfo.getEncoded(); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSCompressedDataGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSCompressedDataGenerator.java new file mode 100644 index 000000000..b26e84a32 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSCompressedDataGenerator.java @@ -0,0 +1,74 @@ +package org.spongycastle.cms; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.BEROctetString; +import org.spongycastle.asn1.cms.CMSObjectIdentifiers; +import org.spongycastle.asn1.cms.CompressedData; +import org.spongycastle.asn1.cms.ContentInfo; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.operator.OutputCompressor; + +/** + * General class for generating a compressed CMS message. + * <p> + * A simple example of usage. + * <p> + * <pre> + * CMSCompressedDataGenerator fact = new CMSCompressedDataGenerator(); + * + * CMSCompressedData data = fact.generate(content, new ZlibCompressor()); + * </pre> + */ +public class CMSCompressedDataGenerator +{ + public static final String ZLIB = "1.2.840.113549.1.9.16.3.8"; + + /** + * base constructor + */ + public CMSCompressedDataGenerator() + { + } + + /** + * generate an object that contains an CMS Compressed Data + */ + public CMSCompressedData generate( + CMSTypedData content, + OutputCompressor compressor) + throws CMSException + { + AlgorithmIdentifier comAlgId; + ASN1OctetString comOcts; + + try + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + OutputStream zOut = compressor.getOutputStream(bOut); + + content.write(zOut); + + zOut.close(); + + comAlgId = compressor.getAlgorithmIdentifier(); + comOcts = new BEROctetString(bOut.toByteArray()); + } + catch (IOException e) + { + throw new CMSException("exception encoding data.", e); + } + + ContentInfo comContent = new ContentInfo( + content.getContentType(), comOcts); + + ContentInfo contentInfo = new ContentInfo( + CMSObjectIdentifiers.compressedData, + new CompressedData(comAlgId, comContent)); + + return new CMSCompressedData(contentInfo); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSCompressedDataParser.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSCompressedDataParser.java new file mode 100644 index 000000000..bc853f2ac --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSCompressedDataParser.java @@ -0,0 +1,72 @@ +package org.spongycastle.cms; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.spongycastle.asn1.ASN1OctetStringParser; +import org.spongycastle.asn1.ASN1SequenceParser; +import org.spongycastle.asn1.BERTags; +import org.spongycastle.asn1.cms.CompressedDataParser; +import org.spongycastle.asn1.cms.ContentInfoParser; +import org.spongycastle.operator.InputExpander; +import org.spongycastle.operator.InputExpanderProvider; + +/** + * Class for reading a CMS Compressed Data stream. + * <pre> + * CMSCompressedDataParser cp = new CMSCompressedDataParser(inputStream); + * + * process(cp.getContent(new ZlibExpanderProvider()).getContentStream()); + * </pre> + * Note: this class does not introduce buffering - if you are processing large files you should create + * the parser with: + * <pre> + * CMSCompressedDataParser ep = new CMSCompressedDataParser(new BufferedInputStream(inputStream, bufSize)); + * </pre> + * where bufSize is a suitably large buffer size. + */ +public class CMSCompressedDataParser + extends CMSContentInfoParser +{ + public CMSCompressedDataParser( + byte[] compressedData) + throws CMSException + { + this(new ByteArrayInputStream(compressedData)); + } + + public CMSCompressedDataParser( + InputStream compressedData) + throws CMSException + { + super(compressedData); + } + + /** + * Return a typed stream which will allow the reading of the compressed content in + * expanded form. + * + * @param expanderProvider a provider of expander algorithm implementations. + * @return a type stream which will yield the un-compressed content. + * @throws CMSException if there is an exception parsing the CompressedData object. + */ + public CMSTypedStream getContent(InputExpanderProvider expanderProvider) + throws CMSException + { + try + { + CompressedDataParser comData = new CompressedDataParser((ASN1SequenceParser)_contentInfo.getContent(BERTags.SEQUENCE)); + ContentInfoParser content = comData.getEncapContentInfo(); + InputExpander expander = expanderProvider.get(comData.getCompressionAlgorithmIdentifier()); + + ASN1OctetStringParser bytes = (ASN1OctetStringParser)content.getContent(BERTags.OCTET_STRING); + + return new CMSTypedStream(content.getContentType().getId(), expander.getInputStream(bytes.getOctetStream())); + } + catch (IOException e) + { + throw new CMSException("IOException reading compressed content.", e); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSCompressedDataStreamGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSCompressedDataStreamGenerator.java new file mode 100644 index 000000000..47af9b4f7 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSCompressedDataStreamGenerator.java @@ -0,0 +1,165 @@ +package org.spongycastle.cms; + +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.BERSequenceGenerator; +import org.spongycastle.asn1.cms.CMSObjectIdentifiers; +import org.spongycastle.operator.OutputCompressor; + +/** + * General class for generating a compressed CMS message stream. + * <p> + * A simple example of usage. + * </p> + * <pre> + * CMSCompressedDataStreamGenerator gen = new CMSCompressedDataStreamGenerator(); + * + * OutputStream cOut = gen.open(outputStream, new ZlibCompressor()); + * + * cOut.write(data); + * + * cOut.close(); + * </pre> + */ +public class CMSCompressedDataStreamGenerator +{ + public static final String ZLIB = "1.2.840.113549.1.9.16.3.8"; + + private int _bufferSize; + + /** + * base constructor + */ + public CMSCompressedDataStreamGenerator() + { + } + + /** + * Set the underlying string size for encapsulated data + * + * @param bufferSize length of octet strings to buffer the data. + */ + public void setBufferSize( + int bufferSize) + { + _bufferSize = bufferSize; + } + + /** + * Open a compressing output stream with the PKCS#7 content type OID of "data". + * + * @param out the stream to encode to. + * @param compressor the type of compressor to use. + * @return an output stream to write the data be compressed to. + * @throws IOException + */ + public OutputStream open( + OutputStream out, + OutputCompressor compressor) + throws IOException + { + return open(CMSObjectIdentifiers.data, out, compressor); + } + + /** + * Open a compressing output stream. + * + * @param contentOID the content type OID. + * @param out the stream to encode to. + * @param compressor the type of compressor to use. + * @return an output stream to write the data be compressed to. + * @throws IOException + */ + public OutputStream open( + ASN1ObjectIdentifier contentOID, + OutputStream out, + OutputCompressor compressor) + throws IOException + { + BERSequenceGenerator sGen = new BERSequenceGenerator(out); + + sGen.addObject(CMSObjectIdentifiers.compressedData); + + // + // Compressed Data + // + BERSequenceGenerator cGen = new BERSequenceGenerator(sGen.getRawOutputStream(), 0, true); + + cGen.addObject(new ASN1Integer(0)); + + // + // AlgorithmIdentifier + // + cGen.addObject(compressor.getAlgorithmIdentifier()); + + // + // Encapsulated ContentInfo + // + BERSequenceGenerator eiGen = new BERSequenceGenerator(cGen.getRawOutputStream()); + + eiGen.addObject(contentOID); + + OutputStream octetStream = CMSUtils.createBEROctetOutputStream( + eiGen.getRawOutputStream(), 0, true, _bufferSize); + + return new CmsCompressedOutputStream( + compressor.getOutputStream(octetStream), sGen, cGen, eiGen); + } + + private class CmsCompressedOutputStream + extends OutputStream + { + private OutputStream _out; + private BERSequenceGenerator _sGen; + private BERSequenceGenerator _cGen; + private BERSequenceGenerator _eiGen; + + CmsCompressedOutputStream( + OutputStream out, + BERSequenceGenerator sGen, + BERSequenceGenerator cGen, + BERSequenceGenerator eiGen) + { + _out = out; + _sGen = sGen; + _cGen = cGen; + _eiGen = eiGen; + } + + public void write( + int b) + throws IOException + { + _out.write(b); + } + + + public void write( + byte[] bytes, + int off, + int len) + throws IOException + { + _out.write(bytes, off, len); + } + + public void write( + byte[] bytes) + throws IOException + { + _out.write(bytes); + } + + public void close() + throws IOException + { + _out.close(); + _eiGen.close(); + _cGen.close(); + _sGen.close(); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSConfig.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSConfig.java new file mode 100644 index 000000000..d53a984ff --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSConfig.java @@ -0,0 +1,34 @@ +package org.spongycastle.cms; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; + +public class CMSConfig +{ + /** + * Set the mapping for the encryption algorithm used in association with a SignedData generation + * or interpretation. + * + * @param oid object identifier to map. + * @param algorithmName algorithm name to use. + */ + public static void setSigningEncryptionAlgorithmMapping(String oid, String algorithmName) + { + ASN1ObjectIdentifier id = new ASN1ObjectIdentifier(oid); + + CMSSignedHelper.INSTANCE.setSigningEncryptionAlgorithmMapping(id, algorithmName); + } + + /** + * Set the mapping for the digest algorithm to use in conjunction with a SignedData generation + * or interpretation. + * + * @param oid object identifier to map. + * @param algorithmName algorithm name to use. + */ + public static void setSigningDigestAlgorithmMapping(String oid, String algorithmName) + { + ASN1ObjectIdentifier id = new ASN1ObjectIdentifier(oid); + + CMSSignedHelper.INSTANCE.setSigningDigestAlgorithmMapping(id, algorithmName); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSContentInfoParser.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSContentInfoParser.java new file mode 100644 index 000000000..10ef9ffdf --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSContentInfoParser.java @@ -0,0 +1,45 @@ +package org.spongycastle.cms; + +import java.io.IOException; +import java.io.InputStream; + +import org.spongycastle.asn1.ASN1SequenceParser; +import org.spongycastle.asn1.ASN1StreamParser; +import org.spongycastle.asn1.cms.ContentInfoParser; + +public class CMSContentInfoParser +{ + protected ContentInfoParser _contentInfo; + protected InputStream _data; + + protected CMSContentInfoParser( + InputStream data) + throws CMSException + { + _data = data; + + try + { + ASN1StreamParser in = new ASN1StreamParser(data); + + _contentInfo = new ContentInfoParser((ASN1SequenceParser)in.readObject()); + } + catch (IOException e) + { + throw new CMSException("IOException reading content.", e); + } + catch (ClassCastException e) + { + throw new CMSException("Unexpected object reading content.", e); + } + } + + /** + * Close the underlying data stream. + * @throws IOException if the close fails. + */ + public void close() throws IOException + { + _data.close(); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSDigestedData.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSDigestedData.java new file mode 100644 index 000000000..fb07fb53b --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSDigestedData.java @@ -0,0 +1,136 @@ +package org.spongycastle.cms; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.cms.ContentInfo; +import org.spongycastle.asn1.cms.DigestedData; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.operator.DigestCalculator; +import org.spongycastle.operator.DigestCalculatorProvider; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.util.Arrays; + +/** + * containing class for an CMS Digested Data object + * <pre> + * CMSDigestedData cd = new CMSDigestedData(inputStream); + * + * + * process(cd.getContent()); + * </pre> + */ +public class CMSDigestedData +{ + private ContentInfo contentInfo; + private DigestedData digestedData; + + public CMSDigestedData( + byte[] compressedData) + throws CMSException + { + this(CMSUtils.readContentInfo(compressedData)); + } + + public CMSDigestedData( + InputStream compressedData) + throws CMSException + { + this(CMSUtils.readContentInfo(compressedData)); + } + + public CMSDigestedData( + ContentInfo contentInfo) + throws CMSException + { + this.contentInfo = contentInfo; + + try + { + this.digestedData = DigestedData.getInstance(contentInfo.getContent()); + } + catch (ClassCastException e) + { + throw new CMSException("Malformed content.", e); + } + catch (IllegalArgumentException e) + { + throw new CMSException("Malformed content.", e); + } + } + + public ASN1ObjectIdentifier getContentType() + { + return contentInfo.getContentType(); + } + + public AlgorithmIdentifier getDigestAlgorithm() + { + return digestedData.getDigestAlgorithm(); + } + + /** + * Return the digested content + * + * @return the digested content + * @throws CMSException if there is an exception un-compressing the data. + */ + public CMSProcessable getDigestedContent() + throws CMSException + { + ContentInfo content = digestedData.getEncapContentInfo(); + + try + { + return new CMSProcessableByteArray(content.getContentType(), ((ASN1OctetString)content.getContent()).getOctets()); + } + catch (Exception e) + { + throw new CMSException("exception reading digested stream.", e); + } + } + + /** + * return the ContentInfo + */ + public ContentInfo toASN1Structure() + { + return contentInfo; + } + + /** + * return the ASN.1 encoded representation of this object. + */ + public byte[] getEncoded() + throws IOException + { + return contentInfo.getEncoded(); + } + + public boolean verify(DigestCalculatorProvider calculatorProvider) + throws CMSException + { + try + { + ContentInfo content = digestedData.getEncapContentInfo(); + DigestCalculator calc = calculatorProvider.get(digestedData.getDigestAlgorithm()); + + OutputStream dOut = calc.getOutputStream(); + + dOut.write(((ASN1OctetString)content.getContent()).getOctets()); + + return Arrays.areEqual(digestedData.getDigest(), calc.getDigest()); + } + catch (OperatorCreationException e) + { + throw new CMSException("unable to create digest calculator: " + e.getMessage(), e); + } + catch (IOException e) + { + throw new CMSException("unable process content: " + e.getMessage(), e); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSEncryptedData.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSEncryptedData.java new file mode 100644 index 000000000..c833610ac --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSEncryptedData.java @@ -0,0 +1,62 @@ +package org.spongycastle.cms; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +import org.spongycastle.asn1.cms.ContentInfo; +import org.spongycastle.asn1.cms.EncryptedContentInfo; +import org.spongycastle.asn1.cms.EncryptedData; +import org.spongycastle.operator.InputDecryptor; +import org.spongycastle.operator.InputDecryptorProvider; + +public class CMSEncryptedData +{ + private ContentInfo contentInfo; + private EncryptedData encryptedData; + + public CMSEncryptedData(ContentInfo contentInfo) + { + this.contentInfo = contentInfo; + + this.encryptedData = EncryptedData.getInstance(contentInfo.getContent()); + } + + public byte[] getContent(InputDecryptorProvider inputDecryptorProvider) + throws CMSException + { + try + { + return CMSUtils.streamToByteArray(getContentStream(inputDecryptorProvider).getContentStream()); + } + catch (IOException e) + { + throw new CMSException("unable to parse internal stream: " + e.getMessage(), e); + } + } + + public CMSTypedStream getContentStream(InputDecryptorProvider inputDecryptorProvider) + throws CMSException + { + try + { + EncryptedContentInfo encContentInfo = encryptedData.getEncryptedContentInfo(); + InputDecryptor decrytor = inputDecryptorProvider.get(encContentInfo.getContentEncryptionAlgorithm()); + + ByteArrayInputStream encIn = new ByteArrayInputStream(encContentInfo.getEncryptedContent().getOctets()); + + return new CMSTypedStream(encContentInfo.getContentType(), decrytor.getInputStream(encIn)); + } + catch (Exception e) + { + throw new CMSException("unable to create stream: " + e.getMessage(), e); + } + } + + /** + * return the ContentInfo + */ + public ContentInfo toASN1Structure() + { + return contentInfo; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSEncryptedDataGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSEncryptedDataGenerator.java new file mode 100644 index 000000000..b2a750870 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSEncryptedDataGenerator.java @@ -0,0 +1,109 @@ +package org.spongycastle.cms; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.HashMap; + +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.ASN1Set; +import org.spongycastle.asn1.BEROctetString; +import org.spongycastle.asn1.BERSet; +import org.spongycastle.asn1.cms.AttributeTable; +import org.spongycastle.asn1.cms.CMSObjectIdentifiers; +import org.spongycastle.asn1.cms.ContentInfo; +import org.spongycastle.asn1.cms.EncryptedContentInfo; +import org.spongycastle.asn1.cms.EncryptedData; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.operator.OutputEncryptor; + +/** + * General class for generating a CMS enveloped-data message. + * + * A simple example of usage. + * + * <pre> + * CMSTypedData msg = new CMSProcessableByteArray("Hello World!".getBytes()); + * + * CMSEncryptedDataGenerator edGen = new CMSEnvelopedDataGenerator(); + * + * CMSEncryptedData ed = edGen.generate( + * msg, + * new JceCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC) + * .setProvider("SC").build()); + * + * </pre> + */ +public class CMSEncryptedDataGenerator + extends CMSEncryptedGenerator +{ + /** + * base constructor + */ + public CMSEncryptedDataGenerator() + { + } + + private CMSEncryptedData doGenerate( + CMSTypedData content, + OutputEncryptor contentEncryptor) + throws CMSException + { + AlgorithmIdentifier encAlgId; + ASN1OctetString encContent; + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + try + { + OutputStream cOut = contentEncryptor.getOutputStream(bOut); + + content.write(cOut); + + cOut.close(); + } + catch (IOException e) + { + throw new CMSException(""); + } + + byte[] encryptedContent = bOut.toByteArray(); + + encAlgId = contentEncryptor.getAlgorithmIdentifier(); + + encContent = new BEROctetString(encryptedContent); + + EncryptedContentInfo eci = new EncryptedContentInfo( + content.getContentType(), + encAlgId, + encContent); + + ASN1Set unprotectedAttrSet = null; + if (unprotectedAttributeGenerator != null) + { + AttributeTable attrTable = unprotectedAttributeGenerator.getAttributes(new HashMap()); + + unprotectedAttrSet = new BERSet(attrTable.toASN1EncodableVector()); + } + + ContentInfo contentInfo = new ContentInfo( + CMSObjectIdentifiers.encryptedData, + new EncryptedData(eci, unprotectedAttrSet)); + + return new CMSEncryptedData(contentInfo); + } + + /** + * generate an encrypted object that contains an CMS Encrypted Data structure. + * + * @param content the content to be encrypted + * @param contentEncryptor the symmetric key based encryptor to encrypt the content with. + */ + public CMSEncryptedData generate( + CMSTypedData content, + OutputEncryptor contentEncryptor) + throws CMSException + { + return doGenerate(content, contentEncryptor); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSEncryptedGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSEncryptedGenerator.java new file mode 100644 index 000000000..cce7a6d43 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSEncryptedGenerator.java @@ -0,0 +1,21 @@ +package org.spongycastle.cms; + +/** + * General class for generating a CMS encrypted-data message. + */ +public class CMSEncryptedGenerator +{ + protected CMSAttributeTableGenerator unprotectedAttributeGenerator = null; + + /** + * base constructor + */ + protected CMSEncryptedGenerator() + { + } + + public void setUnprotectedAttributeGenerator(CMSAttributeTableGenerator unprotectedAttributeGenerator) + { + this.unprotectedAttributeGenerator = unprotectedAttributeGenerator; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSEnvelopedData.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSEnvelopedData.java new file mode 100644 index 000000000..cc4cd30c9 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSEnvelopedData.java @@ -0,0 +1,206 @@ +package org.spongycastle.cms; + +import java.io.IOException; +import java.io.InputStream; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1Set; +import org.spongycastle.asn1.cms.AttributeTable; +import org.spongycastle.asn1.cms.ContentInfo; +import org.spongycastle.asn1.cms.EncryptedContentInfo; +import org.spongycastle.asn1.cms.EnvelopedData; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +/** + * containing class for an CMS Enveloped Data object + * <p> + * Example of use - assuming the first recipient matches the private key we have. + * <pre> + * CMSEnvelopedData ed = new CMSEnvelopedData(inputStream); + * + * RecipientInformationStore recipients = ed.getRecipientInfos(); + * + * Collection c = recipients.getRecipients(); + * Iterator it = c.iterator(); + * + * if (it.hasNext()) + * { + * RecipientInformation recipient = (RecipientInformation)it.next(); + * + * byte[] recData = recipient.getContent(new JceKeyTransEnvelopedRecipient(privateKey).setProvider("SC")); + * + * processData(recData); + * } + * </pre> + */ +public class CMSEnvelopedData +{ + RecipientInformationStore recipientInfoStore; + ContentInfo contentInfo; + + private AlgorithmIdentifier encAlg; + private ASN1Set unprotectedAttributes; + private OriginatorInformation originatorInfo; + + public CMSEnvelopedData( + byte[] envelopedData) + throws CMSException + { + this(CMSUtils.readContentInfo(envelopedData)); + } + + public CMSEnvelopedData( + InputStream envelopedData) + throws CMSException + { + this(CMSUtils.readContentInfo(envelopedData)); + } + + /** + * Construct a CMSEnvelopedData object from a content info object. + * + * @param contentInfo the contentInfo containing the CMS EnvelopedData object. + * @throws CMSException in the case where malformed content is encountered. + */ + public CMSEnvelopedData( + ContentInfo contentInfo) + throws CMSException + { + this.contentInfo = contentInfo; + + try + { + EnvelopedData envData = EnvelopedData.getInstance(contentInfo.getContent()); + + if (envData.getOriginatorInfo() != null) + { + originatorInfo = new OriginatorInformation(envData.getOriginatorInfo()); + } + + // + // read the recipients + // + ASN1Set recipientInfos = envData.getRecipientInfos(); + + // + // read the encrypted content info + // + EncryptedContentInfo encInfo = envData.getEncryptedContentInfo(); + this.encAlg = encInfo.getContentEncryptionAlgorithm(); + CMSReadable readable = new CMSProcessableByteArray(encInfo.getEncryptedContent().getOctets()); + CMSSecureReadable secureReadable = new CMSEnvelopedHelper.CMSEnvelopedSecureReadable( + this.encAlg, readable); + + // + // build the RecipientInformationStore + // + this.recipientInfoStore = CMSEnvelopedHelper.buildRecipientInformationStore( + recipientInfos, this.encAlg, secureReadable); + + this.unprotectedAttributes = envData.getUnprotectedAttrs(); + } + catch (ClassCastException e) + { + throw new CMSException("Malformed content.", e); + } + catch (IllegalArgumentException e) + { + throw new CMSException("Malformed content.", e); + } + } + + private byte[] encodeObj( + ASN1Encodable obj) + throws IOException + { + if (obj != null) + { + return obj.toASN1Primitive().getEncoded(); + } + + return null; + } + + /** + * Return the originator information associated with this message if present. + * + * @return OriginatorInformation, null if not present. + */ + public OriginatorInformation getOriginatorInfo() + { + return originatorInfo; + } + + /** + * Return the content encryption algorithm details for the data in this object. + * + * @return AlgorithmIdentifier representing the content encryption algorithm. + */ + public AlgorithmIdentifier getContentEncryptionAlgorithm() + { + return encAlg; + } + + /** + * return the object identifier for the content encryption algorithm. + */ + public String getEncryptionAlgOID() + { + return encAlg.getAlgorithm().getId(); + } + + /** + * return the ASN.1 encoded encryption algorithm parameters, or null if + * there aren't any. + */ + public byte[] getEncryptionAlgParams() + { + try + { + return encodeObj(encAlg.getParameters()); + } + catch (Exception e) + { + throw new RuntimeException("exception getting encryption parameters " + e); + } + } + + /** + * return a store of the intended recipients for this message + */ + public RecipientInformationStore getRecipientInfos() + { + return recipientInfoStore; + } + + /** + * return the ContentInfo + */ + public ContentInfo toASN1Structure() + { + return contentInfo; + } + + /** + * return a table of the unprotected attributes indexed by + * the OID of the attribute. + */ + public AttributeTable getUnprotectedAttributes() + { + if (unprotectedAttributes == null) + { + return null; + } + + return new AttributeTable(unprotectedAttributes); + } + + /** + * return the ASN.1 encoded representation of this object. + */ + public byte[] getEncoded() + throws IOException + { + return contentInfo.getEncoded(); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSEnvelopedDataGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSEnvelopedDataGenerator.java new file mode 100644 index 000000000..b23a82544 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSEnvelopedDataGenerator.java @@ -0,0 +1,131 @@ +package org.spongycastle.cms; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Iterator; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.ASN1Set; +import org.spongycastle.asn1.BEROctetString; +import org.spongycastle.asn1.BERSet; +import org.spongycastle.asn1.DERSet; +import org.spongycastle.asn1.cms.AttributeTable; +import org.spongycastle.asn1.cms.CMSObjectIdentifiers; +import org.spongycastle.asn1.cms.ContentInfo; +import org.spongycastle.asn1.cms.EncryptedContentInfo; +import org.spongycastle.asn1.cms.EnvelopedData; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.operator.GenericKey; +import org.spongycastle.operator.OutputEncryptor; + +/** + * General class for generating a CMS enveloped-data message. + * + * A simple example of usage. + * + * <pre> + * CMSTypedData msg = new CMSProcessableByteArray("Hello World!".getBytes()); + * + * CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator(); + * + * edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(recipientCert).setProvider("SC")); + * + * CMSEnvelopedData ed = edGen.generate( + * msg, + * new JceCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC) + * .setProvider("SC").build()); + * + * </pre> + */ +public class CMSEnvelopedDataGenerator + extends CMSEnvelopedGenerator +{ + /** + * base constructor + */ + public CMSEnvelopedDataGenerator() + { + } + + private CMSEnvelopedData doGenerate( + CMSTypedData content, + OutputEncryptor contentEncryptor) + throws CMSException + { + if (!oldRecipientInfoGenerators.isEmpty()) + { + throw new IllegalStateException("can only use addRecipientGenerator() with this method"); + } + + ASN1EncodableVector recipientInfos = new ASN1EncodableVector(); + AlgorithmIdentifier encAlgId; + ASN1OctetString encContent; + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + try + { + OutputStream cOut = contentEncryptor.getOutputStream(bOut); + + content.write(cOut); + + cOut.close(); + } + catch (IOException e) + { + throw new CMSException(""); + } + + byte[] encryptedContent = bOut.toByteArray(); + + encAlgId = contentEncryptor.getAlgorithmIdentifier(); + + encContent = new BEROctetString(encryptedContent); + + GenericKey encKey = contentEncryptor.getKey(); + + for (Iterator it = recipientInfoGenerators.iterator(); it.hasNext();) + { + RecipientInfoGenerator recipient = (RecipientInfoGenerator)it.next(); + + recipientInfos.add(recipient.generate(encKey)); + } + + EncryptedContentInfo eci = new EncryptedContentInfo( + content.getContentType(), + encAlgId, + encContent); + + ASN1Set unprotectedAttrSet = null; + if (unprotectedAttributeGenerator != null) + { + AttributeTable attrTable = unprotectedAttributeGenerator.getAttributes(new HashMap()); + + unprotectedAttrSet = new BERSet(attrTable.toASN1EncodableVector()); + } + + ContentInfo contentInfo = new ContentInfo( + CMSObjectIdentifiers.envelopedData, + new EnvelopedData(originatorInfo, new DERSet(recipientInfos), eci, unprotectedAttrSet)); + + return new CMSEnvelopedData(contentInfo); + } + + /** + * generate an enveloped object that contains an CMS Enveloped Data + * object using the given provider. + * + * @param content the content to be encrypted + * @param contentEncryptor the symmetric key based encryptor to encrypt the content with. + */ + public CMSEnvelopedData generate( + CMSTypedData content, + OutputEncryptor contentEncryptor) + throws CMSException + { + return doGenerate(content, contentEncryptor); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSEnvelopedDataParser.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSEnvelopedDataParser.java new file mode 100644 index 000000000..45d602e2a --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSEnvelopedDataParser.java @@ -0,0 +1,208 @@ +package org.spongycastle.cms; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1OctetStringParser; +import org.spongycastle.asn1.ASN1SequenceParser; +import org.spongycastle.asn1.ASN1Set; +import org.spongycastle.asn1.ASN1SetParser; +import org.spongycastle.asn1.BERTags; +import org.spongycastle.asn1.DERSet; +import org.spongycastle.asn1.cms.AttributeTable; +import org.spongycastle.asn1.cms.EncryptedContentInfoParser; +import org.spongycastle.asn1.cms.EnvelopedDataParser; +import org.spongycastle.asn1.cms.OriginatorInfo; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +/** + * Parsing class for an CMS Enveloped Data object from an input stream. + * <p> + * Note: that because we are in a streaming mode only one recipient can be tried and it is important + * that the methods on the parser are called in the appropriate order. + * </p> + * <p> + * Example of use - assuming the first recipient matches the private key we have. + * <pre> + * CMSEnvelopedDataParser ep = new CMSEnvelopedDataParser(inputStream); + * + * RecipientInformationStore recipients = ep.getRecipientInfos(); + * + * Collection c = recipients.getRecipients(); + * Iterator it = c.iterator(); + * + * if (it.hasNext()) + * { + * RecipientInformation recipient = (RecipientInformation)it.next(); + * + * CMSTypedStream recData = recipient.getContentStream(new JceKeyTransEnvelopedRecipient(privateKey).setProvider("SC")); + * + * processDataStream(recData.getContentStream()); + * } + * </pre> + * Note: this class does not introduce buffering - if you are processing large files you should create + * the parser with: + * <pre> + * CMSEnvelopedDataParser ep = new CMSEnvelopedDataParser(new BufferedInputStream(inputStream, bufSize)); + * </pre> + * where bufSize is a suitably large buffer size. + */ +public class CMSEnvelopedDataParser + extends CMSContentInfoParser +{ + RecipientInformationStore recipientInfoStore; + EnvelopedDataParser envelopedData; + + private AlgorithmIdentifier encAlg; + private AttributeTable unprotectedAttributes; + private boolean attrNotRead; + private OriginatorInformation originatorInfo; + + public CMSEnvelopedDataParser( + byte[] envelopedData) + throws CMSException, IOException + { + this(new ByteArrayInputStream(envelopedData)); + } + + public CMSEnvelopedDataParser( + InputStream envelopedData) + throws CMSException, IOException + { + super(envelopedData); + + this.attrNotRead = true; + this.envelopedData = new EnvelopedDataParser((ASN1SequenceParser)_contentInfo.getContent(BERTags.SEQUENCE)); + + // TODO Validate version? + //DERInteger version = this._envelopedData.getVersion(); + + OriginatorInfo info = this.envelopedData.getOriginatorInfo(); + + if (info != null) + { + this.originatorInfo = new OriginatorInformation(info); + } + + // + // read the recipients + // + ASN1Set recipientInfos = ASN1Set.getInstance(this.envelopedData.getRecipientInfos().toASN1Primitive()); + + // + // read the encrypted content info + // + EncryptedContentInfoParser encInfo = this.envelopedData.getEncryptedContentInfo(); + this.encAlg = encInfo.getContentEncryptionAlgorithm(); + CMSReadable readable = new CMSProcessableInputStream( + ((ASN1OctetStringParser)encInfo.getEncryptedContent(BERTags.OCTET_STRING)).getOctetStream()); + CMSSecureReadable secureReadable = new CMSEnvelopedHelper.CMSEnvelopedSecureReadable( + this.encAlg, readable); + + // + // build the RecipientInformationStore + // + this.recipientInfoStore = CMSEnvelopedHelper.buildRecipientInformationStore( + recipientInfos, this.encAlg, secureReadable); + } + + /** + * return the object identifier for the content encryption algorithm. + */ + public String getEncryptionAlgOID() + { + return encAlg.getAlgorithm().toString(); + } + + /** + * return the ASN.1 encoded encryption algorithm parameters, or null if + * there aren't any. + */ + public byte[] getEncryptionAlgParams() + { + try + { + return encodeObj(encAlg.getParameters()); + } + catch (Exception e) + { + throw new RuntimeException("exception getting encryption parameters " + e); + } + } + + /** + * Return the content encryption algorithm details for the data in this object. + * + * @return AlgorithmIdentifier representing the content encryption algorithm. + */ + public AlgorithmIdentifier getContentEncryptionAlgorithm() + { + return encAlg; + } + + /** + * Return the originator information associated with this message if present. + * + * @return OriginatorInformation, null if not present. + */ + public OriginatorInformation getOriginatorInfo() + { + return originatorInfo; + } + + /** + * return a store of the intended recipients for this message + */ + public RecipientInformationStore getRecipientInfos() + { + return recipientInfoStore; + } + + /** + * return a table of the unprotected attributes indexed by + * the OID of the attribute. + * @exception IOException + */ + public AttributeTable getUnprotectedAttributes() + throws IOException + { + if (unprotectedAttributes == null && attrNotRead) + { + ASN1SetParser set = envelopedData.getUnprotectedAttrs(); + + attrNotRead = false; + + if (set != null) + { + ASN1EncodableVector v = new ASN1EncodableVector(); + ASN1Encodable o; + + while ((o = set.readObject()) != null) + { + ASN1SequenceParser seq = (ASN1SequenceParser)o; + + v.add(seq.toASN1Primitive()); + } + + unprotectedAttributes = new AttributeTable(new DERSet(v)); + } + } + + return unprotectedAttributes; + } + + private byte[] encodeObj( + ASN1Encodable obj) + throws IOException + { + if (obj != null) + { + return obj.toASN1Primitive().getEncoded(); + } + + return null; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSEnvelopedDataStreamGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSEnvelopedDataStreamGenerator.java new file mode 100644 index 000000000..86f0a7c5f --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSEnvelopedDataStreamGenerator.java @@ -0,0 +1,305 @@ +package org.spongycastle.cms; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Iterator; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1Set; +import org.spongycastle.asn1.BERSequenceGenerator; +import org.spongycastle.asn1.BERSet; +import org.spongycastle.asn1.DERSet; +import org.spongycastle.asn1.DERTaggedObject; +import org.spongycastle.asn1.cms.AttributeTable; +import org.spongycastle.asn1.cms.CMSObjectIdentifiers; +import org.spongycastle.asn1.cms.EnvelopedData; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.operator.GenericKey; +import org.spongycastle.operator.OutputEncryptor; + +/** + * General class for generating a CMS enveloped-data message stream. + * <p> + * A simple example of usage. + * <pre> + * CMSEnvelopedDataStreamGenerator edGen = new CMSEnvelopedDataStreamGenerator(); + * + * edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(recipientCert).setProvider("SC")); + * + * ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + * + * OutputStream out = edGen.open( + * bOut, new JceCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC) + * .setProvider("SC").build()); + * out.write(data); + * + * out.close(); + * </pre> + */ +public class CMSEnvelopedDataStreamGenerator + extends CMSEnvelopedGenerator +{ + private ASN1Set _unprotectedAttributes = null; + private int _bufferSize; + private boolean _berEncodeRecipientSet; + + /** + * base constructor + */ + public CMSEnvelopedDataStreamGenerator() + { + } + + /** + * Set the underlying string size for encapsulated data + * + * @param bufferSize length of octet strings to buffer the data. + */ + public void setBufferSize( + int bufferSize) + { + _bufferSize = bufferSize; + } + + /** + * Use a BER Set to store the recipient information + */ + public void setBEREncodeRecipients( + boolean berEncodeRecipientSet) + { + _berEncodeRecipientSet = berEncodeRecipientSet; + } + + private ASN1Integer getVersion() + { + if (originatorInfo != null || _unprotectedAttributes != null) + { + return new ASN1Integer(2); + } + else + { + return new ASN1Integer(0); + } + } + + private OutputStream doOpen( + ASN1ObjectIdentifier dataType, + OutputStream out, + OutputEncryptor encryptor) + throws IOException, CMSException + { + ASN1EncodableVector recipientInfos = new ASN1EncodableVector(); + GenericKey encKey = encryptor.getKey(); + Iterator it = recipientInfoGenerators.iterator(); + + while (it.hasNext()) + { + RecipientInfoGenerator recipient = (RecipientInfoGenerator)it.next(); + + recipientInfos.add(recipient.generate(encKey)); + } + + return open(dataType, out, recipientInfos, encryptor); + } + + protected OutputStream open( + ASN1ObjectIdentifier dataType, + OutputStream out, + ASN1EncodableVector recipientInfos, + OutputEncryptor encryptor) + throws IOException + { + // + // ContentInfo + // + BERSequenceGenerator cGen = new BERSequenceGenerator(out); + + cGen.addObject(CMSObjectIdentifiers.envelopedData); + + // + // Encrypted Data + // + BERSequenceGenerator envGen = new BERSequenceGenerator(cGen.getRawOutputStream(), 0, true); + + envGen.addObject(getVersion()); + + if (originatorInfo != null) + { + envGen.addObject(new DERTaggedObject(false, 0, originatorInfo)); + } + + if (_berEncodeRecipientSet) + { + envGen.getRawOutputStream().write(new BERSet(recipientInfos).getEncoded()); + } + else + { + envGen.getRawOutputStream().write(new DERSet(recipientInfos).getEncoded()); + } + + BERSequenceGenerator eiGen = new BERSequenceGenerator(envGen.getRawOutputStream()); + + eiGen.addObject(dataType); + + AlgorithmIdentifier encAlgId = encryptor.getAlgorithmIdentifier(); + + eiGen.getRawOutputStream().write(encAlgId.getEncoded()); + + OutputStream octetStream = CMSUtils.createBEROctetOutputStream( + eiGen.getRawOutputStream(), 0, false, _bufferSize); + + OutputStream cOut = encryptor.getOutputStream(octetStream); + + return new CmsEnvelopedDataOutputStream(cOut, cGen, envGen, eiGen); + } + + protected OutputStream open( + OutputStream out, + ASN1EncodableVector recipientInfos, + OutputEncryptor encryptor) + throws CMSException + { + try + { + // + // ContentInfo + // + BERSequenceGenerator cGen = new BERSequenceGenerator(out); + + cGen.addObject(CMSObjectIdentifiers.envelopedData); + + // + // Encrypted Data + // + BERSequenceGenerator envGen = new BERSequenceGenerator(cGen.getRawOutputStream(), 0, true); + + ASN1Set recipients; + if (_berEncodeRecipientSet) + { + recipients = new BERSet(recipientInfos); + } + else + { + recipients = new DERSet(recipientInfos); + } + + envGen.addObject(new ASN1Integer(EnvelopedData.calculateVersion(originatorInfo, recipients, _unprotectedAttributes))); + + if (originatorInfo != null) + { + envGen.addObject(new DERTaggedObject(false, 0, originatorInfo)); + } + + envGen.getRawOutputStream().write(recipients.getEncoded()); + + BERSequenceGenerator eiGen = new BERSequenceGenerator(envGen.getRawOutputStream()); + + eiGen.addObject(CMSObjectIdentifiers.data); + + AlgorithmIdentifier encAlgId = encryptor.getAlgorithmIdentifier(); + + eiGen.getRawOutputStream().write(encAlgId.getEncoded()); + + OutputStream octetStream = CMSUtils.createBEROctetOutputStream( + eiGen.getRawOutputStream(), 0, false, _bufferSize); + + return new CmsEnvelopedDataOutputStream(encryptor.getOutputStream(octetStream), cGen, envGen, eiGen); + } + catch (IOException e) + { + throw new CMSException("exception decoding algorithm parameters.", e); + } + } + + /** + * generate an enveloped object that contains an CMS Enveloped Data + * object using the given encryptor. + */ + public OutputStream open( + OutputStream out, + OutputEncryptor encryptor) + throws CMSException, IOException + { + return doOpen(new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId()), out, encryptor); + } + + /** + * generate an enveloped object that contains an CMS Enveloped Data + * object using the given encryptor and marking the data as being of the passed + * in type. + */ + public OutputStream open( + ASN1ObjectIdentifier dataType, + OutputStream out, + OutputEncryptor encryptor) + throws CMSException, IOException + { + return doOpen(dataType, out, encryptor); + } + + private class CmsEnvelopedDataOutputStream + extends OutputStream + { + private OutputStream _out; + private BERSequenceGenerator _cGen; + private BERSequenceGenerator _envGen; + private BERSequenceGenerator _eiGen; + + public CmsEnvelopedDataOutputStream( + OutputStream out, + BERSequenceGenerator cGen, + BERSequenceGenerator envGen, + BERSequenceGenerator eiGen) + { + _out = out; + _cGen = cGen; + _envGen = envGen; + _eiGen = eiGen; + } + + public void write( + int b) + throws IOException + { + _out.write(b); + } + + public void write( + byte[] bytes, + int off, + int len) + throws IOException + { + _out.write(bytes, off, len); + } + + public void write( + byte[] bytes) + throws IOException + { + _out.write(bytes); + } + + public void close() + throws IOException + { + _out.close(); + _eiGen.close(); + + if (unprotectedAttributeGenerator != null) + { + AttributeTable attrTable = unprotectedAttributeGenerator.getAttributes(new HashMap()); + + ASN1Set unprotectedAttrs = new BERSet(attrTable.toASN1EncodableVector()); + + _envGen.addObject(new DERTaggedObject(false, 1, unprotectedAttrs)); + } + + _envGen.close(); + _cGen.close(); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSEnvelopedGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSEnvelopedGenerator.java new file mode 100644 index 000000000..c8702240a --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSEnvelopedGenerator.java @@ -0,0 +1,75 @@ +package org.spongycastle.cms; + +import java.util.ArrayList; +import java.util.List; + +import org.spongycastle.asn1.cms.OriginatorInfo; +import org.spongycastle.asn1.kisa.KISAObjectIdentifiers; +import org.spongycastle.asn1.nist.NISTObjectIdentifiers; +import org.spongycastle.asn1.ntt.NTTObjectIdentifiers; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.x9.X9ObjectIdentifiers; + +/** + * General class for generating a CMS enveloped-data message. + */ +public class CMSEnvelopedGenerator +{ + public static final String DES_EDE3_CBC = PKCSObjectIdentifiers.des_EDE3_CBC.getId(); + public static final String RC2_CBC = PKCSObjectIdentifiers.RC2_CBC.getId(); + public static final String IDEA_CBC = "1.3.6.1.4.1.188.7.1.1.2"; + public static final String CAST5_CBC = "1.2.840.113533.7.66.10"; + public static final String AES128_CBC = NISTObjectIdentifiers.id_aes128_CBC.getId(); + public static final String AES192_CBC = NISTObjectIdentifiers.id_aes192_CBC.getId(); + public static final String AES256_CBC = NISTObjectIdentifiers.id_aes256_CBC.getId(); + public static final String CAMELLIA128_CBC = NTTObjectIdentifiers.id_camellia128_cbc.getId(); + public static final String CAMELLIA192_CBC = NTTObjectIdentifiers.id_camellia192_cbc.getId(); + public static final String CAMELLIA256_CBC = NTTObjectIdentifiers.id_camellia256_cbc.getId(); + public static final String SEED_CBC = KISAObjectIdentifiers.id_seedCBC.getId(); + + public static final String DES_EDE3_WRAP = PKCSObjectIdentifiers.id_alg_CMS3DESwrap.getId(); + public static final String AES128_WRAP = NISTObjectIdentifiers.id_aes128_wrap.getId(); + public static final String AES192_WRAP = NISTObjectIdentifiers.id_aes192_wrap.getId(); + public static final String AES256_WRAP = NISTObjectIdentifiers.id_aes256_wrap.getId(); + public static final String CAMELLIA128_WRAP = NTTObjectIdentifiers.id_camellia128_wrap.getId(); + public static final String CAMELLIA192_WRAP = NTTObjectIdentifiers.id_camellia192_wrap.getId(); + public static final String CAMELLIA256_WRAP = NTTObjectIdentifiers.id_camellia256_wrap.getId(); + public static final String SEED_WRAP = KISAObjectIdentifiers.id_npki_app_cmsSeed_wrap.getId(); + + public static final String ECDH_SHA1KDF = X9ObjectIdentifiers.dhSinglePass_stdDH_sha1kdf_scheme.getId(); + public static final String ECMQV_SHA1KDF = X9ObjectIdentifiers.mqvSinglePass_sha1kdf_scheme.getId(); + + final List oldRecipientInfoGenerators = new ArrayList(); + final List recipientInfoGenerators = new ArrayList(); + + protected CMSAttributeTableGenerator unprotectedAttributeGenerator = null; + + protected OriginatorInfo originatorInfo; + + /** + * base constructor + */ + public CMSEnvelopedGenerator() + { + } + + public void setUnprotectedAttributeGenerator(CMSAttributeTableGenerator unprotectedAttributeGenerator) + { + this.unprotectedAttributeGenerator = unprotectedAttributeGenerator; + } + + public void setOriginatorInfo(OriginatorInformation originatorInfo) + { + this.originatorInfo = originatorInfo.toASN1Structure(); + } + + /** + * Add a generator to produce the recipient info required. + * + * @param recipientGenerator a generator of a recipient info object. + */ + public void addRecipientInfoGenerator(RecipientInfoGenerator recipientGenerator) + { + recipientInfoGenerators.add(recipientGenerator); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSEnvelopedHelper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSEnvelopedHelper.java new file mode 100644 index 000000000..e0a71f933 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSEnvelopedHelper.java @@ -0,0 +1,203 @@ +package org.spongycastle.cms; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1Set; +import org.spongycastle.asn1.cms.KEKRecipientInfo; +import org.spongycastle.asn1.cms.KeyAgreeRecipientInfo; +import org.spongycastle.asn1.cms.KeyTransRecipientInfo; +import org.spongycastle.asn1.cms.PasswordRecipientInfo; +import org.spongycastle.asn1.cms.RecipientInfo; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.operator.DigestCalculator; +import org.spongycastle.util.Integers; + +class CMSEnvelopedHelper +{ + static final CMSEnvelopedHelper INSTANCE = new CMSEnvelopedHelper(); + + private static final Map KEYSIZES = new HashMap(); + private static final Map BASE_CIPHER_NAMES = new HashMap(); + private static final Map CIPHER_ALG_NAMES = new HashMap(); + private static final Map MAC_ALG_NAMES = new HashMap(); + + static + { + KEYSIZES.put(CMSEnvelopedGenerator.DES_EDE3_CBC, Integers.valueOf(192)); + KEYSIZES.put(CMSEnvelopedGenerator.AES128_CBC, Integers.valueOf(128)); + KEYSIZES.put(CMSEnvelopedGenerator.AES192_CBC, Integers.valueOf(192)); + KEYSIZES.put(CMSEnvelopedGenerator.AES256_CBC, Integers.valueOf(256)); + + BASE_CIPHER_NAMES.put(CMSEnvelopedGenerator.DES_EDE3_CBC, "DESEDE"); + BASE_CIPHER_NAMES.put(CMSEnvelopedGenerator.AES128_CBC, "AES"); + BASE_CIPHER_NAMES.put(CMSEnvelopedGenerator.AES192_CBC, "AES"); + BASE_CIPHER_NAMES.put(CMSEnvelopedGenerator.AES256_CBC, "AES"); + + CIPHER_ALG_NAMES.put(CMSEnvelopedGenerator.DES_EDE3_CBC, "DESEDE/CBC/PKCS5Padding"); + CIPHER_ALG_NAMES.put(CMSEnvelopedGenerator.AES128_CBC, "AES/CBC/PKCS5Padding"); + CIPHER_ALG_NAMES.put(CMSEnvelopedGenerator.AES192_CBC, "AES/CBC/PKCS5Padding"); + CIPHER_ALG_NAMES.put(CMSEnvelopedGenerator.AES256_CBC, "AES/CBC/PKCS5Padding"); + + MAC_ALG_NAMES.put(CMSEnvelopedGenerator.DES_EDE3_CBC, "DESEDEMac"); + MAC_ALG_NAMES.put(CMSEnvelopedGenerator.AES128_CBC, "AESMac"); + MAC_ALG_NAMES.put(CMSEnvelopedGenerator.AES192_CBC, "AESMac"); + MAC_ALG_NAMES.put(CMSEnvelopedGenerator.AES256_CBC, "AESMac"); + } + + + + int getKeySize(String oid) + { + Integer keySize = (Integer)KEYSIZES.get(oid); + + if (keySize == null) + { + throw new IllegalArgumentException("no keysize for " + oid); + } + + return keySize.intValue(); + } + + + + static RecipientInformationStore buildRecipientInformationStore( + ASN1Set recipientInfos, AlgorithmIdentifier messageAlgorithm, CMSSecureReadable secureReadable) + { + return buildRecipientInformationStore(recipientInfos, messageAlgorithm, secureReadable, null); + } + + static RecipientInformationStore buildRecipientInformationStore( + ASN1Set recipientInfos, AlgorithmIdentifier messageAlgorithm, CMSSecureReadable secureReadable, AuthAttributesProvider additionalData) + { + List infos = new ArrayList(); + for (int i = 0; i != recipientInfos.size(); i++) + { + RecipientInfo info = RecipientInfo.getInstance(recipientInfos.getObjectAt(i)); + + readRecipientInfo(infos, info, messageAlgorithm, secureReadable, additionalData); + } + return new RecipientInformationStore(infos); + } + + private static void readRecipientInfo( + List infos, RecipientInfo info, AlgorithmIdentifier messageAlgorithm, CMSSecureReadable secureReadable, AuthAttributesProvider additionalData) + { + ASN1Encodable recipInfo = info.getInfo(); + if (recipInfo instanceof KeyTransRecipientInfo) + { + infos.add(new KeyTransRecipientInformation( + (KeyTransRecipientInfo)recipInfo, messageAlgorithm, secureReadable, additionalData)); + } + else if (recipInfo instanceof KEKRecipientInfo) + { + infos.add(new KEKRecipientInformation( + (KEKRecipientInfo)recipInfo, messageAlgorithm, secureReadable, additionalData)); + } + else if (recipInfo instanceof KeyAgreeRecipientInfo) + { + KeyAgreeRecipientInformation.readRecipientInfo(infos, + (KeyAgreeRecipientInfo)recipInfo, messageAlgorithm, secureReadable, additionalData); + } + else if (recipInfo instanceof PasswordRecipientInfo) + { + infos.add(new PasswordRecipientInformation( + (PasswordRecipientInfo)recipInfo, messageAlgorithm, secureReadable, additionalData)); + } + } + + static class CMSDigestAuthenticatedSecureReadable + implements CMSSecureReadable + { + private DigestCalculator digestCalculator; + private CMSReadable readable; + + public CMSDigestAuthenticatedSecureReadable(DigestCalculator digestCalculator, CMSReadable readable) + { + this.digestCalculator = digestCalculator; + this.readable = readable; + } + + public InputStream getInputStream() + throws IOException, CMSException + { + return new FilterInputStream(readable.getInputStream()) + { + public int read() + throws IOException + { + int b = in.read(); + + if (b >= 0) + { + digestCalculator.getOutputStream().write(b); + } + + return b; + } + + public int read(byte[] inBuf, int inOff, int inLen) + throws IOException + { + int n = in.read(inBuf, inOff, inLen); + + if (n >= 0) + { + digestCalculator.getOutputStream().write(inBuf, inOff, n); + } + + return n; + } + }; + } + + public byte[] getDigest() + { + return digestCalculator.getDigest(); + } + } + + static class CMSAuthenticatedSecureReadable implements CMSSecureReadable + { + private AlgorithmIdentifier algorithm; + private CMSReadable readable; + + CMSAuthenticatedSecureReadable(AlgorithmIdentifier algorithm, CMSReadable readable) + { + this.algorithm = algorithm; + this.readable = readable; + } + + public InputStream getInputStream() + throws IOException, CMSException + { + return readable.getInputStream(); + } + + } + + static class CMSEnvelopedSecureReadable implements CMSSecureReadable + { + private AlgorithmIdentifier algorithm; + private CMSReadable readable; + + CMSEnvelopedSecureReadable(AlgorithmIdentifier algorithm, CMSReadable readable) + { + this.algorithm = algorithm; + this.readable = readable; + } + + public InputStream getInputStream() + throws IOException, CMSException + { + return readable.getInputStream(); + } + + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSException.java new file mode 100644 index 000000000..dd8401201 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSException.java @@ -0,0 +1,32 @@ +package org.spongycastle.cms; + +public class CMSException + extends Exception +{ + Exception e; + + public CMSException( + String msg) + { + super(msg); + } + + public CMSException( + String msg, + Exception e) + { + super(msg); + + this.e = e; + } + + public Exception getUnderlyingException() + { + return e; + } + + public Throwable getCause() + { + return e; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSProcessable.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSProcessable.java new file mode 100644 index 000000000..ed27eb159 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSProcessable.java @@ -0,0 +1,21 @@ +package org.spongycastle.cms; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Use CMSTypedData instead of this. See CMSProcessableFile/ByteArray for defaults. + */ +public interface CMSProcessable +{ + /** + * generic routine to copy out the data we want processed - the OutputStream + * passed in will do the handling on it's own. + * <p> + * Note: this routine may be called multiple times. + */ + public void write(OutputStream out) + throws IOException, CMSException; + + public Object getContent(); +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSProcessableByteArray.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSProcessableByteArray.java new file mode 100644 index 000000000..2732a26ed --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSProcessableByteArray.java @@ -0,0 +1,55 @@ +package org.spongycastle.cms; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.cms.CMSObjectIdentifiers; +import org.spongycastle.util.Arrays; + +/** + * a holding class for a byte array of data to be processed. + */ +public class CMSProcessableByteArray + implements CMSTypedData, CMSReadable +{ + private final ASN1ObjectIdentifier type; + private final byte[] bytes; + + public CMSProcessableByteArray( + byte[] bytes) + { + this(new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId()), bytes); + } + + public CMSProcessableByteArray( + ASN1ObjectIdentifier type, + byte[] bytes) + { + this.type = type; + this.bytes = bytes; + } + + public InputStream getInputStream() + { + return new ByteArrayInputStream(bytes); + } + + public void write(OutputStream zOut) + throws IOException, CMSException + { + zOut.write(bytes); + } + + public Object getContent() + { + return Arrays.clone(bytes); + } + + public ASN1ObjectIdentifier getContentType() + { + return type; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSProcessableFile.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSProcessableFile.java new file mode 100644 index 000000000..6415dd5d0 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSProcessableFile.java @@ -0,0 +1,80 @@ +package org.spongycastle.cms; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.cms.CMSObjectIdentifiers; + +/** + * a holding class for a file of data to be processed. + */ +public class CMSProcessableFile + implements CMSTypedData, CMSReadable +{ + private static final int DEFAULT_BUF_SIZE = 32 * 1024; + + private final ASN1ObjectIdentifier type; + private final File file; + private final byte[] buf; + + public CMSProcessableFile( + File file) + { + this(file, DEFAULT_BUF_SIZE); + } + + public CMSProcessableFile( + File file, + int bufSize) + { + this(new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId()), file, bufSize); + } + + public CMSProcessableFile( + ASN1ObjectIdentifier type, + File file, + int bufSize) + { + this.type = type; + this.file = file; + buf = new byte[bufSize]; + } + + public InputStream getInputStream() + throws IOException, CMSException + { + return new BufferedInputStream(new FileInputStream(file), DEFAULT_BUF_SIZE); + } + + public void write(OutputStream zOut) + throws IOException, CMSException + { + FileInputStream fIn = new FileInputStream(file); + int len; + + while ((len = fIn.read(buf, 0, buf.length)) > 0) + { + zOut.write(buf, 0, len); + } + + fIn.close(); + } + + /** + * Return the file handle. + */ + public Object getContent() + { + return file; + } + + public ASN1ObjectIdentifier getContentType() + { + return type; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSProcessableInputStream.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSProcessableInputStream.java new file mode 100644 index 000000000..fc644c957 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSProcessableInputStream.java @@ -0,0 +1,50 @@ +package org.spongycastle.cms; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.spongycastle.util.io.Streams; + +class CMSProcessableInputStream implements CMSProcessable, CMSReadable +{ + private InputStream input; + private boolean used = false; + + public CMSProcessableInputStream( + InputStream input) + { + this.input = input; + } + + public InputStream getInputStream() + { + checkSingleUsage(); + + return input; + } + + public void write(OutputStream zOut) + throws IOException, CMSException + { + checkSingleUsage(); + + Streams.pipeAll(input, zOut); + input.close(); + } + + public Object getContent() + { + return getInputStream(); + } + + private synchronized void checkSingleUsage() + { + if (used) + { + throw new IllegalStateException("CMSProcessableInputStream can only be used once"); + } + + used = true; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSReadable.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSReadable.java new file mode 100644 index 000000000..ecf79a43d --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSReadable.java @@ -0,0 +1,10 @@ +package org.spongycastle.cms; + +import java.io.IOException; +import java.io.InputStream; + +interface CMSReadable +{ + public InputStream getInputStream() + throws IOException, CMSException; +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSRuntimeException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSRuntimeException.java new file mode 100644 index 000000000..29805bb2d --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSRuntimeException.java @@ -0,0 +1,32 @@ +package org.spongycastle.cms; + +public class CMSRuntimeException + extends RuntimeException +{ + Exception e; + + public CMSRuntimeException( + String name) + { + super(name); + } + + public CMSRuntimeException( + String name, + Exception e) + { + super(name); + + this.e = e; + } + + public Exception getUnderlyingException() + { + return e; + } + + public Throwable getCause() + { + return e; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSSecureReadable.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSSecureReadable.java new file mode 100644 index 000000000..b16aef111 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSSecureReadable.java @@ -0,0 +1,10 @@ +package org.spongycastle.cms; + +import java.io.IOException; +import java.io.InputStream; + +interface CMSSecureReadable +{ + InputStream getInputStream() + throws IOException, CMSException; +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSSignatureAlgorithmNameGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSSignatureAlgorithmNameGenerator.java new file mode 100644 index 000000000..8941a90bb --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSSignatureAlgorithmNameGenerator.java @@ -0,0 +1,15 @@ +package org.spongycastle.cms; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +public interface CMSSignatureAlgorithmNameGenerator +{ + /** + * Return the digest algorithm using one of the standard string + * representations rather than the algorithm object identifier (if possible). + * + * @param digestAlg the digest algorithm id. + * @param encryptionAlg the encryption, or signing, algorithm id. + */ + String getSignatureName(AlgorithmIdentifier digestAlg, AlgorithmIdentifier encryptionAlg); +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSSignatureEncryptionAlgorithmFinder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSSignatureEncryptionAlgorithmFinder.java new file mode 100644 index 000000000..886b41015 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSSignatureEncryptionAlgorithmFinder.java @@ -0,0 +1,17 @@ +package org.spongycastle.cms; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +/** + * Finder which is used to look up the algorithm identifiers representing the encryption algorithms that + * are associated with a particular signature algorithm. + */ +public interface CMSSignatureEncryptionAlgorithmFinder +{ + /** + * Return the encryption algorithm identifier associated with the passed in signatureAlgorithm + * @param signatureAlgorithm the algorithm identifier of the signature of interest + * @return the algorithm identifier to be associated with the encryption algorithm used in signature creation. + */ + AlgorithmIdentifier findEncryptionAlgorithm(AlgorithmIdentifier signatureAlgorithm); +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSSignedData.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSSignedData.java new file mode 100644 index 000000000..cea2c955a --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSSignedData.java @@ -0,0 +1,543 @@ +package org.spongycastle.cms; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1InputStream; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.ASN1Set; +import org.spongycastle.asn1.BERSequence; +import org.spongycastle.asn1.DERSet; +import org.spongycastle.asn1.cms.ContentInfo; +import org.spongycastle.asn1.cms.SignedData; +import org.spongycastle.asn1.cms.SignerInfo; +import org.spongycastle.operator.DefaultSignatureAlgorithmIdentifierFinder; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.operator.SignatureAlgorithmIdentifierFinder; +import org.spongycastle.util.Store; + +/** + * general class for handling a pkcs7-signature message. + * + * A simple example of usage - note, in the example below the validity of + * the certificate isn't verified, just the fact that one of the certs + * matches the given signer... + * + * <pre> + * Store certStore = s.getCertificates(); + * SignerInformationStore signers = s.getSignerInfos(); + * Collection c = signers.getSigners(); + * Iterator it = c.iterator(); + * + * while (it.hasNext()) + * { + * SignerInformation signer = (SignerInformation)it.next(); + * Collection certCollection = certStore.getMatches(signer.getSID()); + * + * Iterator certIt = certCollection.iterator(); + * X509CertificateHolder cert = (X509CertificateHolder)certIt.next(); + * + * if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("SC").build(cert))) + * { + * verified++; + * } + * } + * </pre> + */ +public class CMSSignedData +{ + private static final CMSSignedHelper HELPER = CMSSignedHelper.INSTANCE; + + SignedData signedData; + ContentInfo contentInfo; + CMSTypedData signedContent; + SignerInformationStore signerInfoStore; + + private Map hashes; + + private CMSSignedData( + CMSSignedData c) + { + this.signedData = c.signedData; + this.contentInfo = c.contentInfo; + this.signedContent = c.signedContent; + this.signerInfoStore = c.signerInfoStore; + } + + public CMSSignedData( + byte[] sigBlock) + throws CMSException + { + this(CMSUtils.readContentInfo(sigBlock)); + } + + public CMSSignedData( + CMSProcessable signedContent, + byte[] sigBlock) + throws CMSException + { + this(signedContent, CMSUtils.readContentInfo(sigBlock)); + } + + /** + * Content with detached signature, digests precomputed + * + * @param hashes a map of precomputed digests for content indexed by name of hash. + * @param sigBlock the signature object. + */ + public CMSSignedData( + Map hashes, + byte[] sigBlock) + throws CMSException + { + this(hashes, CMSUtils.readContentInfo(sigBlock)); + } + + /** + * base constructor - content with detached signature. + * + * @param signedContent the content that was signed. + * @param sigData the signature object. + */ + public CMSSignedData( + CMSProcessable signedContent, + InputStream sigData) + throws CMSException + { + this(signedContent, CMSUtils.readContentInfo(new ASN1InputStream(sigData))); + } + + /** + * base constructor - with encapsulated content + */ + public CMSSignedData( + InputStream sigData) + throws CMSException + { + this(CMSUtils.readContentInfo(sigData)); + } + + public CMSSignedData( + final CMSProcessable signedContent, + ContentInfo sigData) + throws CMSException + { + if (signedContent instanceof CMSTypedData) + { + this.signedContent = (CMSTypedData)signedContent; + } + else + { + this.signedContent = new CMSTypedData() + { + public ASN1ObjectIdentifier getContentType() + { + return signedData.getEncapContentInfo().getContentType(); + } + + public void write(OutputStream out) + throws IOException, CMSException + { + signedContent.write(out); + } + + public Object getContent() + { + return signedContent.getContent(); + } + }; + } + + this.contentInfo = sigData; + this.signedData = getSignedData(); + } + + public CMSSignedData( + Map hashes, + ContentInfo sigData) + throws CMSException + { + this.hashes = hashes; + this.contentInfo = sigData; + this.signedData = getSignedData(); + } + + public CMSSignedData( + ContentInfo sigData) + throws CMSException + { + this.contentInfo = sigData; + this.signedData = getSignedData(); + + // + // this can happen if the signed message is sent simply to send a + // certificate chain. + // + if (signedData.getEncapContentInfo().getContent() != null) + { + this.signedContent = new CMSProcessableByteArray(signedData.getEncapContentInfo().getContentType(), + ((ASN1OctetString)(signedData.getEncapContentInfo() + .getContent())).getOctets()); + } + else + { + this.signedContent = null; + } + } + + private SignedData getSignedData() + throws CMSException + { + try + { + return SignedData.getInstance(contentInfo.getContent()); + } + catch (ClassCastException e) + { + throw new CMSException("Malformed content.", e); + } + catch (IllegalArgumentException e) + { + throw new CMSException("Malformed content.", e); + } + } + + /** + * Return the version number for this object + */ + public int getVersion() + { + return signedData.getVersion().getValue().intValue(); + } + + /** + * return the collection of signers that are associated with the + * signatures for the message. + */ + public SignerInformationStore getSignerInfos() + { + if (signerInfoStore == null) + { + ASN1Set s = signedData.getSignerInfos(); + List signerInfos = new ArrayList(); + SignatureAlgorithmIdentifierFinder sigAlgFinder = new DefaultSignatureAlgorithmIdentifierFinder(); + + for (int i = 0; i != s.size(); i++) + { + SignerInfo info = SignerInfo.getInstance(s.getObjectAt(i)); + ASN1ObjectIdentifier contentType = signedData.getEncapContentInfo().getContentType(); + + if (hashes == null) + { + signerInfos.add(new SignerInformation(info, contentType, signedContent, null)); + } + else + { + Object obj = hashes.keySet().iterator().next(); + byte[] hash = (obj instanceof String) ? (byte[])hashes.get(info.getDigestAlgorithm().getAlgorithm().getId()) : (byte[])hashes.get(info.getDigestAlgorithm().getAlgorithm()); + + signerInfos.add(new SignerInformation(info, contentType, null, hash)); + } + } + + signerInfoStore = new SignerInformationStore(signerInfos); + } + + return signerInfoStore; + } + + /** + * Return any X.509 certificate objects in this SignedData structure as a Store of X509CertificateHolder objects. + * + * @return a Store of X509CertificateHolder objects. + */ + public Store getCertificates() + { + return HELPER.getCertificates(signedData.getCertificates()); + } + + /** + * Return any X.509 CRL objects in this SignedData structure as a Store of X509CRLHolder objects. + * + * @return a Store of X509CRLHolder objects. + */ + public Store getCRLs() + { + return HELPER.getCRLs(signedData.getCRLs()); + } + + /** + * Return any X.509 attribute certificate objects in this SignedData structure as a Store of X509AttributeCertificateHolder objects. + * + * @return a Store of X509AttributeCertificateHolder objects. + */ + public Store getAttributeCertificates() + { + return HELPER.getAttributeCertificates(signedData.getCertificates()); + } + + /** + * Return any OtherRevocationInfo OtherRevInfo objects of the type indicated by otherRevocationInfoFormat in + * this SignedData structure. + * + * @param otherRevocationInfoFormat OID of the format type been looked for. + * + * @return a Store of ASN1Encodable objects representing any objects of otherRevocationInfoFormat found. + */ + public Store getOtherRevocationInfo(ASN1ObjectIdentifier otherRevocationInfoFormat) + { + return HELPER.getOtherRevocationInfo(otherRevocationInfoFormat, signedData.getCRLs()); + } + + /** + * Return the a string representation of the OID associated with the + * encapsulated content info structure carried in the signed data. + * + * @return the OID for the content type. + */ + public String getSignedContentTypeOID() + { + return signedData.getEncapContentInfo().getContentType().getId(); + } + + public CMSTypedData getSignedContent() + { + return signedContent; + } + + /** + * return the ContentInfo + */ + public ContentInfo toASN1Structure() + { + return contentInfo; + } + + /** + * return the ASN.1 encoded representation of this object. + */ + public byte[] getEncoded() + throws IOException + { + return contentInfo.getEncoded(); + } + + /** + * Verify all the SignerInformation objects and their associated counter signatures attached + * to this CMS SignedData object. + * + * @param verifierProvider a provider of SignerInformationVerifier objects. + * @return true if all verify, false otherwise. + * @throws CMSException if an exception occurs during the verification process. + */ + public boolean verifySignatures(SignerInformationVerifierProvider verifierProvider) + throws CMSException + { + return verifySignatures(verifierProvider, false); + } + + /** + * Verify all the SignerInformation objects and optionally their associated counter signatures attached + * to this CMS SignedData object. + * + * @param verifierProvider a provider of SignerInformationVerifier objects. + * @param ignoreCounterSignatures if true don't check counter signatures. If false check counter signatures as well. + * @return true if all verify, false otherwise. + * @throws CMSException if an exception occurs during the verification process. + */ + public boolean verifySignatures(SignerInformationVerifierProvider verifierProvider, boolean ignoreCounterSignatures) + throws CMSException + { + Collection signers = this.getSignerInfos().getSigners(); + + for (Iterator it = signers.iterator(); it.hasNext();) + { + SignerInformation signer = (SignerInformation)it.next(); + + try + { + SignerInformationVerifier verifier = verifierProvider.get(signer.getSID()); + + if (!signer.verify(verifier)) + { + return false; + } + + if (!ignoreCounterSignatures) + { + Collection counterSigners = signer.getCounterSignatures().getSigners(); + + for (Iterator cIt = counterSigners.iterator(); cIt.hasNext();) + { + SignerInformation counterSigner = (SignerInformation)cIt.next(); + SignerInformationVerifier counterVerifier = verifierProvider.get(signer.getSID()); + + if (!counterSigner.verify(counterVerifier)) + { + return false; + } + } + } + } + catch (OperatorCreationException e) + { + throw new CMSException("failure in verifier provider: " + e.getMessage(), e); + } + } + + return true; + } + + /** + * Replace the SignerInformation store associated with this + * CMSSignedData object with the new one passed in. You would + * probably only want to do this if you wanted to change the unsigned + * attributes associated with a signer, or perhaps delete one. + * + * @param signedData the signed data object to be used as a base. + * @param signerInformationStore the new signer information store to use. + * @return a new signed data object. + */ + public static CMSSignedData replaceSigners( + CMSSignedData signedData, + SignerInformationStore signerInformationStore) + { + // + // copy + // + CMSSignedData cms = new CMSSignedData(signedData); + + // + // replace the store + // + cms.signerInfoStore = signerInformationStore; + + // + // replace the signers in the SignedData object + // + ASN1EncodableVector digestAlgs = new ASN1EncodableVector(); + ASN1EncodableVector vec = new ASN1EncodableVector(); + + Iterator it = signerInformationStore.getSigners().iterator(); + while (it.hasNext()) + { + SignerInformation signer = (SignerInformation)it.next(); + digestAlgs.add(CMSSignedHelper.INSTANCE.fixAlgID(signer.getDigestAlgorithmID())); + vec.add(signer.toASN1Structure()); + } + + ASN1Set digests = new DERSet(digestAlgs); + ASN1Set signers = new DERSet(vec); + ASN1Sequence sD = (ASN1Sequence)signedData.signedData.toASN1Primitive(); + + vec = new ASN1EncodableVector(); + + // + // signers are the last item in the sequence. + // + vec.add(sD.getObjectAt(0)); // version + vec.add(digests); + + for (int i = 2; i != sD.size() - 1; i++) + { + vec.add(sD.getObjectAt(i)); + } + + vec.add(signers); + + cms.signedData = SignedData.getInstance(new BERSequence(vec)); + + // + // replace the contentInfo with the new one + // + cms.contentInfo = new ContentInfo(cms.contentInfo.getContentType(), cms.signedData); + + return cms; + } + + /** + * Replace the certificate and CRL information associated with this + * CMSSignedData object with the new one passed in. + * + * @param signedData the signed data object to be used as a base. + * @param certificates the new certificates to be used. + * @param attrCerts the new attribute certificates to be used. + * @param crls the new CRLs to be used. + * @return a new signed data object. + * @exception CMSException if there is an error processing the CertStore + */ + public static CMSSignedData replaceCertificatesAndCRLs( + CMSSignedData signedData, + Store certificates, + Store attrCerts, + Store crls) + throws CMSException + { + // + // copy + // + CMSSignedData cms = new CMSSignedData(signedData); + + // + // replace the certs and crls in the SignedData object + // + ASN1Set certSet = null; + ASN1Set crlSet = null; + + if (certificates != null || attrCerts != null) + { + List certs = new ArrayList(); + + if (certificates != null) + { + certs.addAll(CMSUtils.getCertificatesFromStore(certificates)); + } + if (attrCerts != null) + { + certs.addAll(CMSUtils.getAttributeCertificatesFromStore(attrCerts)); + } + + ASN1Set set = CMSUtils.createBerSetFromList(certs); + + if (set.size() != 0) + { + certSet = set; + } + } + + if (crls != null) + { + ASN1Set set = CMSUtils.createBerSetFromList(CMSUtils.getCRLsFromStore(crls)); + + if (set.size() != 0) + { + crlSet = set; + } + } + + // + // replace the CMS structure. + // + cms.signedData = new SignedData(signedData.signedData.getDigestAlgorithms(), + signedData.signedData.getEncapContentInfo(), + certSet, + crlSet, + signedData.signedData.getSignerInfos()); + + // + // replace the contentInfo with the new one + // + cms.contentInfo = new ContentInfo(cms.contentInfo.getContentType(), cms.signedData); + + return cms; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSSignedDataGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSSignedDataGenerator.java new file mode 100644 index 000000000..529ca14a9 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSSignedDataGenerator.java @@ -0,0 +1,232 @@ +package org.spongycastle.cms; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.ASN1Set; +import org.spongycastle.asn1.BEROctetString; +import org.spongycastle.asn1.DERSet; +import org.spongycastle.asn1.cms.CMSObjectIdentifiers; +import org.spongycastle.asn1.cms.ContentInfo; +import org.spongycastle.asn1.cms.SignedData; +import org.spongycastle.asn1.cms.SignerInfo; + +/** + * general class for generating a pkcs7-signature message. + * <p> + * A simple example of usage, generating a detached signature. + * + * <pre> + * List certList = new ArrayList(); + * CMSTypedData msg = new CMSProcessableByteArray("Hello world!".getBytes()); + * + * certList.add(signCert); + * + * Store certs = new JcaCertStore(certList); + * + * CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); + * ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("SC").build(signKP.getPrivate()); + * + * gen.addSignerInfoGenerator( + * new JcaSignerInfoGeneratorBuilder( + * new JcaDigestCalculatorProviderBuilder().setProvider("SC").build()) + * .build(sha1Signer, signCert)); + * + * gen.addCertificates(certs); + * + * CMSSignedData sigData = gen.generate(msg, false); + * </pre> + */ +public class CMSSignedDataGenerator + extends CMSSignedGenerator +{ + private List signerInfs = new ArrayList(); + + /** + * base constructor + */ + public CMSSignedDataGenerator() + { + } + + /** + * Generate a CMS Signed Data object carrying a detached CMS signature. + * + * @param content the content to be signed. + */ + public CMSSignedData generate( + CMSTypedData content) + throws CMSException + { + return generate(content, false); + } + + /** + * Generate a CMS Signed Data object which can be carrying a detached CMS signature, or have encapsulated data, depending on the value + * of the encapsulated parameter. + * + * @param content the content to be signed. + * @param encapsulate true if the content should be encapsulated in the signature, false otherwise. + */ + public CMSSignedData generate( + // FIXME Avoid accessing more than once to support CMSProcessableInputStream + CMSTypedData content, + boolean encapsulate) + throws CMSException + { + if (!signerInfs.isEmpty()) + { + throw new IllegalStateException("this method can only be used with SignerInfoGenerator"); + } + + // TODO +// if (signerInfs.isEmpty()) +// { +// /* RFC 3852 5.2 +// * "In the degenerate case where there are no signers, the +// * EncapsulatedContentInfo value being "signed" is irrelevant. In this +// * case, the content type within the EncapsulatedContentInfo value being +// * "signed" MUST be id-data (as defined in section 4), and the content +// * field of the EncapsulatedContentInfo value MUST be omitted." +// */ +// if (encapsulate) +// { +// throw new IllegalArgumentException("no signers, encapsulate must be false"); +// } +// if (!DATA.equals(eContentType)) +// { +// throw new IllegalArgumentException("no signers, eContentType must be id-data"); +// } +// } +// +// if (!DATA.equals(eContentType)) +// { +// /* RFC 3852 5.3 +// * [The 'signedAttrs']... +// * field is optional, but it MUST be present if the content type of +// * the EncapsulatedContentInfo value being signed is not id-data. +// */ +// // TODO signedAttrs must be present for all signers +// } + + ASN1EncodableVector digestAlgs = new ASN1EncodableVector(); + ASN1EncodableVector signerInfos = new ASN1EncodableVector(); + + digests.clear(); // clear the current preserved digest state + + // + // add the precalculated SignerInfo objects. + // + for (Iterator it = _signers.iterator(); it.hasNext();) + { + SignerInformation signer = (SignerInformation)it.next(); + digestAlgs.add(CMSSignedHelper.INSTANCE.fixAlgID(signer.getDigestAlgorithmID())); + + // TODO Verify the content type and calculated digest match the precalculated SignerInfo + signerInfos.add(signer.toASN1Structure()); + } + + // + // add the SignerInfo objects + // + ASN1ObjectIdentifier contentTypeOID = content.getContentType(); + + ASN1OctetString octs = null; + + if (content != null) + { + ByteArrayOutputStream bOut = null; + + if (encapsulate) + { + bOut = new ByteArrayOutputStream(); + } + + OutputStream cOut = CMSUtils.attachSignersToOutputStream(signerGens, bOut); + + // Just in case it's unencapsulated and there are no signers! + cOut = CMSUtils.getSafeOutputStream(cOut); + + try + { + content.write(cOut); + + cOut.close(); + } + catch (IOException e) + { + throw new CMSException("data processing exception: " + e.getMessage(), e); + } + + if (encapsulate) + { + octs = new BEROctetString(bOut.toByteArray()); + } + } + + for (Iterator it = signerGens.iterator(); it.hasNext();) + { + SignerInfoGenerator sGen = (SignerInfoGenerator)it.next(); + SignerInfo inf = sGen.generate(contentTypeOID); + + digestAlgs.add(inf.getDigestAlgorithm()); + signerInfos.add(inf); + + byte[] calcDigest = sGen.getCalculatedDigest(); + + if (calcDigest != null) + { + digests.put(inf.getDigestAlgorithm().getAlgorithm().getId(), calcDigest); + } + } + + ASN1Set certificates = null; + + if (certs.size() != 0) + { + certificates = CMSUtils.createBerSetFromList(certs); + } + + ASN1Set certrevlist = null; + + if (crls.size() != 0) + { + certrevlist = CMSUtils.createBerSetFromList(crls); + } + + ContentInfo encInfo = new ContentInfo(contentTypeOID, octs); + + SignedData sd = new SignedData( + new DERSet(digestAlgs), + encInfo, + certificates, + certrevlist, + new DERSet(signerInfos)); + + ContentInfo contentInfo = new ContentInfo( + CMSObjectIdentifiers.signedData, sd); + + return new CMSSignedData(content, contentInfo); + } + + /** + * generate a set of one or more SignerInformation objects representing counter signatures on + * the passed in SignerInformation object. + * + * @param signer the signer to be countersigned + * @return a store containing the signers. + */ + public SignerInformationStore generateCounterSigners(SignerInformation signer) + throws CMSException + { + return this.generate(new CMSProcessableByteArray(null, signer.getSignature()), false).getSignerInfos(); + } +} + diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSSignedDataParser.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSSignedDataParser.java new file mode 100644 index 000000000..89edf8734 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSSignedDataParser.java @@ -0,0 +1,624 @@ +package org.spongycastle.cms; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Generator; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1OctetStringParser; +import org.spongycastle.asn1.ASN1SequenceParser; +import org.spongycastle.asn1.ASN1Set; +import org.spongycastle.asn1.ASN1SetParser; +import org.spongycastle.asn1.ASN1StreamParser; +import org.spongycastle.asn1.BERSequenceGenerator; +import org.spongycastle.asn1.BERSetParser; +import org.spongycastle.asn1.BERTaggedObject; +import org.spongycastle.asn1.BERTags; +import org.spongycastle.asn1.DERSet; +import org.spongycastle.asn1.DERTaggedObject; +import org.spongycastle.asn1.cms.CMSObjectIdentifiers; +import org.spongycastle.asn1.cms.ContentInfoParser; +import org.spongycastle.asn1.cms.SignedDataParser; +import org.spongycastle.asn1.cms.SignerInfo; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.operator.DigestCalculator; +import org.spongycastle.operator.DigestCalculatorProvider; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.util.Store; +import org.spongycastle.util.io.Streams; + +/** + * Parsing class for an CMS Signed Data object from an input stream. + * <p> + * Note: that because we are in a streaming mode only one signer can be tried and it is important + * that the methods on the parser are called in the appropriate order. + * </p> + * <p> + * A simple example of usage for an encapsulated signature. + * </p> + * <p> + * Two notes: first, in the example below the validity of + * the certificate isn't verified, just the fact that one of the certs + * matches the given signer, and, second, because we are in a streaming + * mode the order of the operations is important. + * </p> + * <pre> + * CMSSignedDataParser sp = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider("SC").build(), encapSigData); + * + * sp.getSignedContent().drain(); + * + * Store certStore = sp.getCertificates(); + * SignerInformationStore signers = sp.getSignerInfos(); + * + * Collection c = signers.getSigners(); + * Iterator it = c.iterator(); + * + * while (it.hasNext()) + * { + * SignerInformation signer = (SignerInformation)it.next(); + * Collection certCollection = certStore.getMatches(signer.getSID()); + * + * Iterator certIt = certCollection.iterator(); + * X509CertificateHolder cert = (X509CertificateHolder)certIt.next(); + * + * System.out.println("verify returns: " + signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("SC").build(cert))); + * } + * </pre> + * Note also: this class does not introduce buffering - if you are processing large files you should create + * the parser with: + * <pre> + * CMSSignedDataParser ep = new CMSSignedDataParser(new BufferedInputStream(encapSigData, bufSize)); + * </pre> + * where bufSize is a suitably large buffer size. + */ +public class CMSSignedDataParser + extends CMSContentInfoParser +{ + private static final CMSSignedHelper HELPER = CMSSignedHelper.INSTANCE; + + private SignedDataParser _signedData; + private ASN1ObjectIdentifier _signedContentType; + private CMSTypedStream _signedContent; + private Map digests; + + private SignerInformationStore _signerInfoStore; + private ASN1Set _certSet, _crlSet; + private boolean _isCertCrlParsed; + + public CMSSignedDataParser( + DigestCalculatorProvider digestCalculatorProvider, + byte[] sigBlock) + throws CMSException + { + this(digestCalculatorProvider, new ByteArrayInputStream(sigBlock)); + } + + public CMSSignedDataParser( + DigestCalculatorProvider digestCalculatorProvider, + CMSTypedStream signedContent, + byte[] sigBlock) + throws CMSException + { + this(digestCalculatorProvider, signedContent, new ByteArrayInputStream(sigBlock)); + } + + /** + * base constructor - with encapsulated content + */ + public CMSSignedDataParser( + DigestCalculatorProvider digestCalculatorProvider, + InputStream sigData) + throws CMSException + { + this(digestCalculatorProvider, null, sigData); + } + + /** + * base constructor + * + * @param digestCalculatorProvider for generating accumulating digests + * @param signedContent the content that was signed. + * @param sigData the signature object stream. + */ + public CMSSignedDataParser( + DigestCalculatorProvider digestCalculatorProvider, + CMSTypedStream signedContent, + InputStream sigData) + throws CMSException + { + super(sigData); + + try + { + _signedContent = signedContent; + _signedData = SignedDataParser.getInstance(_contentInfo.getContent(BERTags.SEQUENCE)); + digests = new HashMap(); + + ASN1SetParser digAlgs = _signedData.getDigestAlgorithms(); + ASN1Encodable o; + + while ((o = digAlgs.readObject()) != null) + { + AlgorithmIdentifier algId = AlgorithmIdentifier.getInstance(o); + try + { + DigestCalculator calculator = digestCalculatorProvider.get(algId); + + if (calculator != null) + { + this.digests.put(algId.getAlgorithm(), calculator); + } + } + catch (OperatorCreationException e) + { + // ignore + } + } + + // + // If the message is simply a certificate chain message getContent() may return null. + // + ContentInfoParser cont = _signedData.getEncapContentInfo(); + ASN1OctetStringParser octs = (ASN1OctetStringParser) + cont.getContent(BERTags.OCTET_STRING); + + if (octs != null) + { + CMSTypedStream ctStr = new CMSTypedStream( + cont.getContentType().getId(), octs.getOctetStream()); + + if (_signedContent == null) + { + _signedContent = ctStr; + } + else + { + // + // content passed in, need to read past empty encapsulated content info object if present + // + ctStr.drain(); + } + } + + if (signedContent == null) + { + _signedContentType = cont.getContentType(); + } + else + { + _signedContentType = _signedContent.getContentType(); + } + } + catch (IOException e) + { + throw new CMSException("io exception: " + e.getMessage(), e); + } + } + + /** + * Return the version number for the SignedData object + * + * @return the version number + */ + public int getVersion() + { + return _signedData.getVersion().getValue().intValue(); + } + + /** + * return the collection of signers that are associated with the + * signatures for the message. + * @throws CMSException + */ + public SignerInformationStore getSignerInfos() + throws CMSException + { + if (_signerInfoStore == null) + { + populateCertCrlSets(); + + List signerInfos = new ArrayList(); + Map hashes = new HashMap(); + + Iterator it = digests.keySet().iterator(); + while (it.hasNext()) + { + Object digestKey = it.next(); + + hashes.put(digestKey, ((DigestCalculator)digests.get(digestKey)).getDigest()); + } + + try + { + ASN1SetParser s = _signedData.getSignerInfos(); + ASN1Encodable o; + + while ((o = s.readObject()) != null) + { + SignerInfo info = SignerInfo.getInstance(o.toASN1Primitive()); + + byte[] hash = (byte[])hashes.get(info.getDigestAlgorithm().getAlgorithm()); + + signerInfos.add(new SignerInformation(info, _signedContentType, null, hash)); + } + } + catch (IOException e) + { + throw new CMSException("io exception: " + e.getMessage(), e); + } + + _signerInfoStore = new SignerInformationStore(signerInfos); + } + + return _signerInfoStore; + } + + /** + * Return any X.509 certificate objects in this SignedData structure as a Store of X509CertificateHolder objects. + * + * @return a Store of X509CertificateHolder objects. + */ + public Store getCertificates() + throws CMSException + { + populateCertCrlSets(); + + return HELPER.getCertificates(_certSet); + } + + /** + * Return any X.509 CRL objects in this SignedData structure as a Store of X509CRLHolder objects. + * + * @return a Store of X509CRLHolder objects. + */ + public Store getCRLs() + throws CMSException + { + populateCertCrlSets(); + + return HELPER.getCRLs(_crlSet); + } + + /** + * Return any X.509 attribute certificate objects in this SignedData structure as a Store of X509AttributeCertificateHolder objects. + * + * @return a Store of X509AttributeCertificateHolder objects. + */ + public Store getAttributeCertificates() + throws CMSException + { + populateCertCrlSets(); + + return HELPER.getAttributeCertificates(_certSet); + } + + /** + * Return any OtherRevocationInfo OtherRevInfo objects of the type indicated by otherRevocationInfoFormat in + * this SignedData structure. + * + * @param otherRevocationInfoFormat OID of the format type been looked for. + * + * @return a Store of ASN1Encodable objects representing any objects of otherRevocationInfoFormat found. + */ + public Store getOtherRevocationInfo(ASN1ObjectIdentifier otherRevocationInfoFormat) + throws CMSException + { + populateCertCrlSets(); + + return HELPER.getOtherRevocationInfo(otherRevocationInfoFormat, _crlSet); + } + + private void populateCertCrlSets() + throws CMSException + { + if (_isCertCrlParsed) + { + return; + } + + _isCertCrlParsed = true; + + try + { + // care! Streaming - these must be done in exactly this order. + _certSet = getASN1Set(_signedData.getCertificates()); + _crlSet = getASN1Set(_signedData.getCrls()); + } + catch (IOException e) + { + throw new CMSException("problem parsing cert/crl sets", e); + } + } + + /** + * Return the a string representation of the OID associated with the + * encapsulated content info structure carried in the signed data. + * + * @return the OID for the content type. + */ + public String getSignedContentTypeOID() + { + return _signedContentType.getId(); + } + + public CMSTypedStream getSignedContent() + { + if (_signedContent == null) + { + return null; + } + + InputStream digStream = CMSUtils.attachDigestsToInputStream( + digests.values(), _signedContent.getContentStream()); + + return new CMSTypedStream(_signedContent.getContentType(), digStream); + } + + /** + * Replace the signerinformation store associated with the passed + * in message contained in the stream original with the new one passed in. + * You would probably only want to do this if you wanted to change the unsigned + * attributes associated with a signer, or perhaps delete one. + * <p> + * The output stream is returned unclosed. + * </p> + * @param original the signed data stream to be used as a base. + * @param signerInformationStore the new signer information store to use. + * @param out the stream to write the new signed data object to. + * @return out. + */ + public static OutputStream replaceSigners( + InputStream original, + SignerInformationStore signerInformationStore, + OutputStream out) + throws CMSException, IOException + { + ASN1StreamParser in = new ASN1StreamParser(original); + ContentInfoParser contentInfo = new ContentInfoParser((ASN1SequenceParser)in.readObject()); + SignedDataParser signedData = SignedDataParser.getInstance(contentInfo.getContent(BERTags.SEQUENCE)); + + BERSequenceGenerator sGen = new BERSequenceGenerator(out); + + sGen.addObject(CMSObjectIdentifiers.signedData); + + BERSequenceGenerator sigGen = new BERSequenceGenerator(sGen.getRawOutputStream(), 0, true); + + // version number + sigGen.addObject(signedData.getVersion()); + + // digests + signedData.getDigestAlgorithms().toASN1Primitive(); // skip old ones + + ASN1EncodableVector digestAlgs = new ASN1EncodableVector(); + + for (Iterator it = signerInformationStore.getSigners().iterator(); it.hasNext();) + { + SignerInformation signer = (SignerInformation)it.next(); + digestAlgs.add(CMSSignedHelper.INSTANCE.fixAlgID(signer.getDigestAlgorithmID())); + } + + sigGen.getRawOutputStream().write(new DERSet(digestAlgs).getEncoded()); + + // encap content info + ContentInfoParser encapContentInfo = signedData.getEncapContentInfo(); + + BERSequenceGenerator eiGen = new BERSequenceGenerator(sigGen.getRawOutputStream()); + + eiGen.addObject(encapContentInfo.getContentType()); + + pipeEncapsulatedOctetString(encapContentInfo, eiGen.getRawOutputStream()); + + eiGen.close(); + + + writeSetToGeneratorTagged(sigGen, signedData.getCertificates(), 0); + writeSetToGeneratorTagged(sigGen, signedData.getCrls(), 1); + + + ASN1EncodableVector signerInfos = new ASN1EncodableVector(); + for (Iterator it = signerInformationStore.getSigners().iterator(); it.hasNext();) + { + SignerInformation signer = (SignerInformation)it.next(); + + signerInfos.add(signer.toASN1Structure()); + } + + sigGen.getRawOutputStream().write(new DERSet(signerInfos).getEncoded()); + + sigGen.close(); + + sGen.close(); + + return out; + } + + /** + * Replace the certificate and CRL information associated with this + * CMSSignedData object with the new one passed in. + * <p> + * The output stream is returned unclosed. + * </p> + * @param original the signed data stream to be used as a base. + * @param certs new certificates to be used, if any. + * @param crls new CRLs to be used, if any. + * @param attrCerts new attribute certificates to be used, if any. + * @param out the stream to write the new signed data object to. + * @return out. + * @exception CMSException if there is an error processing the CertStore + */ + public static OutputStream replaceCertificatesAndCRLs( + InputStream original, + Store certs, + Store crls, + Store attrCerts, + OutputStream out) + throws CMSException, IOException + { + ASN1StreamParser in = new ASN1StreamParser(original); + ContentInfoParser contentInfo = new ContentInfoParser((ASN1SequenceParser)in.readObject()); + SignedDataParser signedData = SignedDataParser.getInstance(contentInfo.getContent(BERTags.SEQUENCE)); + + BERSequenceGenerator sGen = new BERSequenceGenerator(out); + + sGen.addObject(CMSObjectIdentifiers.signedData); + + BERSequenceGenerator sigGen = new BERSequenceGenerator(sGen.getRawOutputStream(), 0, true); + + // version number + sigGen.addObject(signedData.getVersion()); + + // digests + sigGen.getRawOutputStream().write(signedData.getDigestAlgorithms().toASN1Primitive().getEncoded()); + + // encap content info + ContentInfoParser encapContentInfo = signedData.getEncapContentInfo(); + + BERSequenceGenerator eiGen = new BERSequenceGenerator(sigGen.getRawOutputStream()); + + eiGen.addObject(encapContentInfo.getContentType()); + + pipeEncapsulatedOctetString(encapContentInfo, eiGen.getRawOutputStream()); + + eiGen.close(); + + // + // skip existing certs and CRLs + // + getASN1Set(signedData.getCertificates()); + getASN1Set(signedData.getCrls()); + + // + // replace the certs and crls in the SignedData object + // + if (certs != null || attrCerts != null) + { + List certificates = new ArrayList(); + + if (certs != null) + { + certificates.addAll(CMSUtils.getCertificatesFromStore(certs)); + } + if (attrCerts != null) + { + certificates.addAll(CMSUtils.getAttributeCertificatesFromStore(attrCerts)); + } + + ASN1Set asn1Certs = CMSUtils.createBerSetFromList(certificates); + + if (asn1Certs.size() > 0) + { + sigGen.getRawOutputStream().write(new DERTaggedObject(false, 0, asn1Certs).getEncoded()); + } + } + + if (crls != null) + { + ASN1Set asn1Crls = CMSUtils.createBerSetFromList(CMSUtils.getCRLsFromStore(crls)); + + if (asn1Crls.size() > 0) + { + sigGen.getRawOutputStream().write(new DERTaggedObject(false, 1, asn1Crls).getEncoded()); + } + } + + sigGen.getRawOutputStream().write(signedData.getSignerInfos().toASN1Primitive().getEncoded()); + + sigGen.close(); + + sGen.close(); + + return out; + } + + private static void writeSetToGeneratorTagged( + ASN1Generator asn1Gen, + ASN1SetParser asn1SetParser, + int tagNo) + throws IOException + { + ASN1Set asn1Set = getASN1Set(asn1SetParser); + + if (asn1Set != null) + { + if (asn1SetParser instanceof BERSetParser) + { + asn1Gen.getRawOutputStream().write(new BERTaggedObject(false, tagNo, asn1Set).getEncoded()); + } + else + { + asn1Gen.getRawOutputStream().write(new DERTaggedObject(false, tagNo, asn1Set).getEncoded()); + } + } + } + + private static ASN1Set getASN1Set( + ASN1SetParser asn1SetParser) + { + return asn1SetParser == null + ? null + : ASN1Set.getInstance(asn1SetParser.toASN1Primitive()); + } + + private static void pipeEncapsulatedOctetString(ContentInfoParser encapContentInfo, + OutputStream rawOutputStream) throws IOException + { + ASN1OctetStringParser octs = (ASN1OctetStringParser) + encapContentInfo.getContent(BERTags.OCTET_STRING); + + if (octs != null) + { + pipeOctetString(octs, rawOutputStream); + } + +// BERTaggedObjectParser contentObject = (BERTaggedObjectParser)encapContentInfo.getContentObject(); +// if (contentObject != null) +// { +// // Handle IndefiniteLengthInputStream safely +// InputStream input = ASN1StreamParser.getSafeRawInputStream(contentObject.getContentStream(true)); +// +// // TODO BerTaggedObjectGenerator? +// BEROutputStream berOut = new BEROutputStream(rawOutputStream); +// berOut.write(DERTags.CONSTRUCTED | DERTags.TAGGED | 0); +// berOut.write(0x80); +// +// pipeRawOctetString(input, rawOutputStream); +// +// berOut.write(0x00); +// berOut.write(0x00); +// +// input.close(); +// } + } + + private static void pipeOctetString( + ASN1OctetStringParser octs, + OutputStream output) + throws IOException + { + // TODO Allow specification of a specific fragment size? + OutputStream outOctets = CMSUtils.createBEROctetOutputStream( + output, 0, true, 0); + Streams.pipeAll(octs.getOctetStream(), outOctets); + outOctets.close(); + } + +// private static void pipeRawOctetString( +// InputStream rawInput, +// OutputStream rawOutput) +// throws IOException +// { +// InputStream tee = new TeeInputStream(rawInput, rawOutput); +// ASN1StreamParser sp = new ASN1StreamParser(tee); +// ASN1OctetStringParser octs = (ASN1OctetStringParser)sp.readObject(); +// Streams.drain(octs.getOctetStream()); +// } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSSignedDataStreamGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSSignedDataStreamGenerator.java new file mode 100644 index 000000000..15a94bf58 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSSignedDataStreamGenerator.java @@ -0,0 +1,486 @@ +package org.spongycastle.cms; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Iterator; +import java.util.List; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1Set; +import org.spongycastle.asn1.ASN1TaggedObject; +import org.spongycastle.asn1.BERSequenceGenerator; +import org.spongycastle.asn1.BERTaggedObject; +import org.spongycastle.asn1.DERSet; +import org.spongycastle.asn1.cms.CMSObjectIdentifiers; +import org.spongycastle.asn1.cms.SignerInfo; + +/** + * General class for generating a pkcs7-signature message stream. + * <p> + * A simple example of usage. + * </p> + * <pre> + * X509Certificate signCert = ... + * certList.add(signCert); + * + * Store certs = new JcaCertStore(certList); + * ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("SC").build(signKP.getPrivate()); + * + * CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator(); + * + * gen.addSignerInfoGenerator( + * new JcaSignerInfoGeneratorBuilder( + * new JcaDigestCalculatorProviderBuilder().setProvider("SC").build()) + * .build(sha1Signer, signCert)); + * + * gen.addCertificates(certs); + * + * OutputStream sigOut = gen.open(bOut); + * + * sigOut.write("Hello World!".getBytes()); + * + * sigOut.close(); + * </pre> + */ +public class CMSSignedDataStreamGenerator + extends CMSSignedGenerator +{ + private int _bufferSize; + + /** + * base constructor + */ + public CMSSignedDataStreamGenerator() + { + } + + /** + * Set the underlying string size for encapsulated data + * + * @param bufferSize length of octet strings to buffer the data. + */ + public void setBufferSize( + int bufferSize) + { + _bufferSize = bufferSize; + } + + /** + * generate a signed object that for a CMS Signed Data + * object using the given provider. + */ + public OutputStream open( + OutputStream out) + throws IOException + { + return open(out, false); + } + + /** + * generate a signed object that for a CMS Signed Data + * object using the given provider - if encapsulate is true a copy + * of the message will be included in the signature with the + * default content type "data". + */ + public OutputStream open( + OutputStream out, + boolean encapsulate) + throws IOException + { + return open(CMSObjectIdentifiers.data, out, encapsulate); + } + + /** + * generate a signed object that for a CMS Signed Data + * object using the given provider - if encapsulate is true a copy + * of the message will be included in the signature with the + * default content type "data". If dataOutputStream is non null the data + * being signed will be written to the stream as it is processed. + * @param out stream the CMS object is to be written to. + * @param encapsulate true if data should be encapsulated. + * @param dataOutputStream output stream to copy the data being signed to. + */ + public OutputStream open( + OutputStream out, + boolean encapsulate, + OutputStream dataOutputStream) + throws IOException + { + return open(CMSObjectIdentifiers.data, out, encapsulate, dataOutputStream); + } + + /** + * generate a signed object that for a CMS Signed Data + * object using the given provider - if encapsulate is true a copy + * of the message will be included in the signature. The content type + * is set according to the OID represented by the string signedContentType. + */ + public OutputStream open( + ASN1ObjectIdentifier eContentType, + OutputStream out, + boolean encapsulate) + throws IOException + { + return open(eContentType, out, encapsulate, null); + } + + /** + * generate a signed object that for a CMS Signed Data + * object using the given provider - if encapsulate is true a copy + * of the message will be included in the signature. The content type + * is set according to the OID represented by the string signedContentType. + * @param eContentType OID for data to be signed. + * @param out stream the CMS object is to be written to. + * @param encapsulate true if data should be encapsulated. + * @param dataOutputStream output stream to copy the data being signed to. + */ + public OutputStream open( + ASN1ObjectIdentifier eContentType, + OutputStream out, + boolean encapsulate, + OutputStream dataOutputStream) + throws IOException + { + // TODO +// if (_signerInfs.isEmpty()) +// { +// /* RFC 3852 5.2 +// * "In the degenerate case where there are no signers, the +// * EncapsulatedContentInfo value being "signed" is irrelevant. In this +// * case, the content type within the EncapsulatedContentInfo value being +// * "signed" MUST be id-data (as defined in section 4), and the content +// * field of the EncapsulatedContentInfo value MUST be omitted." +// */ +// if (encapsulate) +// { +// throw new IllegalArgumentException("no signers, encapsulate must be false"); +// } +// if (!DATA.equals(eContentType)) +// { +// throw new IllegalArgumentException("no signers, eContentType must be id-data"); +// } +// } +// +// if (!DATA.equals(eContentType)) +// { +// /* RFC 3852 5.3 +// * [The 'signedAttrs']... +// * field is optional, but it MUST be present if the content type of +// * the EncapsulatedContentInfo value being signed is not id-data. +// */ +// // TODO signedAttrs must be present for all signers +// } + + // + // ContentInfo + // + BERSequenceGenerator sGen = new BERSequenceGenerator(out); + + sGen.addObject(CMSObjectIdentifiers.signedData); + + // + // Signed Data + // + BERSequenceGenerator sigGen = new BERSequenceGenerator(sGen.getRawOutputStream(), 0, true); + + sigGen.addObject(calculateVersion(eContentType)); + + ASN1EncodableVector digestAlgs = new ASN1EncodableVector(); + + // + // add the precalculated SignerInfo digest algorithms. + // + for (Iterator it = _signers.iterator(); it.hasNext();) + { + SignerInformation signer = (SignerInformation)it.next(); + digestAlgs.add(CMSSignedHelper.INSTANCE.fixAlgID(signer.getDigestAlgorithmID())); + } + + // + // add the new digests + // + + for (Iterator it = signerGens.iterator(); it.hasNext();) + { + SignerInfoGenerator signerGen = (SignerInfoGenerator)it.next(); + + digestAlgs.add(signerGen.getDigestAlgorithm()); + } + + sigGen.getRawOutputStream().write(new DERSet(digestAlgs).getEncoded()); + + BERSequenceGenerator eiGen = new BERSequenceGenerator(sigGen.getRawOutputStream()); + eiGen.addObject(eContentType); + + // If encapsulating, add the data as an octet string in the sequence + OutputStream encapStream = encapsulate + ? CMSUtils.createBEROctetOutputStream(eiGen.getRawOutputStream(), 0, true, _bufferSize) + : null; + + // Also send the data to 'dataOutputStream' if necessary + OutputStream contentStream = CMSUtils.getSafeTeeOutputStream(dataOutputStream, encapStream); + + // Let all the signers see the data as it is written + OutputStream sigStream = CMSUtils.attachSignersToOutputStream(signerGens, contentStream); + + return new CmsSignedDataOutputStream(sigStream, eContentType, sGen, sigGen, eiGen); + } + + // RFC3852, section 5.1: + // IF ((certificates is present) AND + // (any certificates with a type of other are present)) OR + // ((crls is present) AND + // (any crls with a type of other are present)) + // THEN version MUST be 5 + // ELSE + // IF (certificates is present) AND + // (any version 2 attribute certificates are present) + // THEN version MUST be 4 + // ELSE + // IF ((certificates is present) AND + // (any version 1 attribute certificates are present)) OR + // (any SignerInfo structures are version 3) OR + // (encapContentInfo eContentType is other than id-data) + // THEN version MUST be 3 + // ELSE version MUST be 1 + // + private ASN1Integer calculateVersion( + ASN1ObjectIdentifier contentOid) + { + boolean otherCert = false; + boolean otherCrl = false; + boolean attrCertV1Found = false; + boolean attrCertV2Found = false; + + if (certs != null) + { + for (Iterator it = certs.iterator(); it.hasNext();) + { + Object obj = it.next(); + if (obj instanceof ASN1TaggedObject) + { + ASN1TaggedObject tagged = (ASN1TaggedObject)obj; + + if (tagged.getTagNo() == 1) + { + attrCertV1Found = true; + } + else if (tagged.getTagNo() == 2) + { + attrCertV2Found = true; + } + else if (tagged.getTagNo() == 3) + { + otherCert = true; + } + } + } + } + + if (otherCert) + { + return new ASN1Integer(5); + } + + if (crls != null) // no need to check if otherCert is true + { + for (Iterator it = crls.iterator(); it.hasNext();) + { + Object obj = it.next(); + if (obj instanceof ASN1TaggedObject) + { + otherCrl = true; + } + } + } + + if (otherCrl) + { + return new ASN1Integer(5); + } + + if (attrCertV2Found) + { + return new ASN1Integer(4); + } + + if (attrCertV1Found) + { + return new ASN1Integer(3); + } + + if (checkForVersion3(_signers, signerGens)) + { + return new ASN1Integer(3); + } + + if (!CMSObjectIdentifiers.data.equals(contentOid)) + { + return new ASN1Integer(3); + } + + return new ASN1Integer(1); + } + + private boolean checkForVersion3(List signerInfos, List signerInfoGens) + { + for (Iterator it = signerInfos.iterator(); it.hasNext();) + { + SignerInfo s = SignerInfo.getInstance(((SignerInformation)it.next()).toASN1Structure()); + + if (s.getVersion().getValue().intValue() == 3) + { + return true; + } + } + + for (Iterator it = signerInfoGens.iterator(); it.hasNext();) + { + SignerInfoGenerator s = (SignerInfoGenerator)it.next(); + + if (s.getGeneratedVersion() == 3) + { + return true; + } + } + + return false; + } + + private class CmsSignedDataOutputStream + extends OutputStream + { + private OutputStream _out; + private ASN1ObjectIdentifier _contentOID; + private BERSequenceGenerator _sGen; + private BERSequenceGenerator _sigGen; + private BERSequenceGenerator _eiGen; + + public CmsSignedDataOutputStream( + OutputStream out, + ASN1ObjectIdentifier contentOID, + BERSequenceGenerator sGen, + BERSequenceGenerator sigGen, + BERSequenceGenerator eiGen) + { + _out = out; + _contentOID = contentOID; + _sGen = sGen; + _sigGen = sigGen; + _eiGen = eiGen; + } + + public void write( + int b) + throws IOException + { + _out.write(b); + } + + public void write( + byte[] bytes, + int off, + int len) + throws IOException + { + _out.write(bytes, off, len); + } + + public void write( + byte[] bytes) + throws IOException + { + _out.write(bytes); + } + + public void close() + throws IOException + { + _out.close(); + _eiGen.close(); + + digests.clear(); // clear the current preserved digest state + + if (certs.size() != 0) + { + ASN1Set certSet = CMSUtils.createBerSetFromList(certs); + + _sigGen.getRawOutputStream().write(new BERTaggedObject(false, 0, certSet).getEncoded()); + } + + if (crls.size() != 0) + { + ASN1Set crlSet = CMSUtils.createBerSetFromList(crls); + + _sigGen.getRawOutputStream().write(new BERTaggedObject(false, 1, crlSet).getEncoded()); + } + + // + // collect all the SignerInfo objects + // + ASN1EncodableVector signerInfos = new ASN1EncodableVector(); + + // + // add the generated SignerInfo objects + // + + for (Iterator it = signerGens.iterator(); it.hasNext();) + { + SignerInfoGenerator sigGen = (SignerInfoGenerator)it.next(); + + + try + { + signerInfos.add(sigGen.generate(_contentOID)); + + byte[] calculatedDigest = sigGen.getCalculatedDigest(); + + digests.put(sigGen.getDigestAlgorithm().getAlgorithm().getId(), calculatedDigest); + } + catch (CMSException e) + { + throw new CMSStreamException("exception generating signers: " + e.getMessage(), e); + } + } + + // + // add the precalculated SignerInfo objects + // + { + Iterator it = _signers.iterator(); + while (it.hasNext()) + { + SignerInformation signer = (SignerInformation)it.next(); + + // TODO Verify the content type and calculated digest match the precalculated SignerInfo +// if (!signer.getContentType().equals(_contentOID)) +// { +// // TODO The precalculated content type did not match - error? +// } +// +// byte[] calculatedDigest = (byte[])_digests.get(signer.getDigestAlgOID()); +// if (calculatedDigest == null) +// { +// // TODO We can't confirm this digest because we didn't calculate it - error? +// } +// else +// { +// if (!Arrays.areEqual(signer.getContentDigest(), calculatedDigest)) +// { +// // TODO The precalculated digest did not match - error? +// } +// } + + signerInfos.add(signer.toASN1Structure()); + } + } + + _sigGen.getRawOutputStream().write(new DERSet(signerInfos).getEncoded()); + + _sigGen.close(); + _sGen.close(); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSSignedGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSSignedGenerator.java new file mode 100644 index 000000000..b2747d9ff --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSSignedGenerator.java @@ -0,0 +1,239 @@ +package org.spongycastle.cms; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.DERTaggedObject; +import org.spongycastle.asn1.cms.CMSObjectIdentifiers; +import org.spongycastle.asn1.cms.OtherRevocationInfoFormat; +import org.spongycastle.asn1.cryptopro.CryptoProObjectIdentifiers; +import org.spongycastle.asn1.nist.NISTObjectIdentifiers; +import org.spongycastle.asn1.oiw.OIWObjectIdentifiers; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.teletrust.TeleTrusTObjectIdentifiers; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x9.X9ObjectIdentifiers; +import org.spongycastle.cert.X509AttributeCertificateHolder; +import org.spongycastle.cert.X509CRLHolder; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.util.Arrays; +import org.spongycastle.util.Store; + +public class CMSSignedGenerator +{ + /** + * Default type for the signed data. + */ + public static final String DATA = CMSObjectIdentifiers.data.getId(); + + public static final String DIGEST_SHA1 = OIWObjectIdentifiers.idSHA1.getId(); + public static final String DIGEST_SHA224 = NISTObjectIdentifiers.id_sha224.getId(); + public static final String DIGEST_SHA256 = NISTObjectIdentifiers.id_sha256.getId(); + public static final String DIGEST_SHA384 = NISTObjectIdentifiers.id_sha384.getId(); + public static final String DIGEST_SHA512 = NISTObjectIdentifiers.id_sha512.getId(); + public static final String DIGEST_MD5 = PKCSObjectIdentifiers.md5.getId(); + public static final String DIGEST_GOST3411 = CryptoProObjectIdentifiers.gostR3411.getId(); + public static final String DIGEST_RIPEMD128 = TeleTrusTObjectIdentifiers.ripemd128.getId(); + public static final String DIGEST_RIPEMD160 = TeleTrusTObjectIdentifiers.ripemd160.getId(); + public static final String DIGEST_RIPEMD256 = TeleTrusTObjectIdentifiers.ripemd256.getId(); + + public static final String ENCRYPTION_RSA = PKCSObjectIdentifiers.rsaEncryption.getId(); + public static final String ENCRYPTION_DSA = X9ObjectIdentifiers.id_dsa_with_sha1.getId(); + public static final String ENCRYPTION_ECDSA = X9ObjectIdentifiers.ecdsa_with_SHA1.getId(); + public static final String ENCRYPTION_RSA_PSS = PKCSObjectIdentifiers.id_RSASSA_PSS.getId(); + public static final String ENCRYPTION_GOST3410 = CryptoProObjectIdentifiers.gostR3410_94.getId(); + public static final String ENCRYPTION_ECGOST3410 = CryptoProObjectIdentifiers.gostR3410_2001.getId(); + + private static final String ENCRYPTION_ECDSA_WITH_SHA1 = X9ObjectIdentifiers.ecdsa_with_SHA1.getId(); + private static final String ENCRYPTION_ECDSA_WITH_SHA224 = X9ObjectIdentifiers.ecdsa_with_SHA224.getId(); + private static final String ENCRYPTION_ECDSA_WITH_SHA256 = X9ObjectIdentifiers.ecdsa_with_SHA256.getId(); + private static final String ENCRYPTION_ECDSA_WITH_SHA384 = X9ObjectIdentifiers.ecdsa_with_SHA384.getId(); + private static final String ENCRYPTION_ECDSA_WITH_SHA512 = X9ObjectIdentifiers.ecdsa_with_SHA512.getId(); + + private static final Set NO_PARAMS = new HashSet(); + private static final Map EC_ALGORITHMS = new HashMap(); + + static + { + NO_PARAMS.add(ENCRYPTION_DSA); + NO_PARAMS.add(ENCRYPTION_ECDSA); + NO_PARAMS.add(ENCRYPTION_ECDSA_WITH_SHA1); + NO_PARAMS.add(ENCRYPTION_ECDSA_WITH_SHA224); + NO_PARAMS.add(ENCRYPTION_ECDSA_WITH_SHA256); + NO_PARAMS.add(ENCRYPTION_ECDSA_WITH_SHA384); + NO_PARAMS.add(ENCRYPTION_ECDSA_WITH_SHA512); + + EC_ALGORITHMS.put(DIGEST_SHA1, ENCRYPTION_ECDSA_WITH_SHA1); + EC_ALGORITHMS.put(DIGEST_SHA224, ENCRYPTION_ECDSA_WITH_SHA224); + EC_ALGORITHMS.put(DIGEST_SHA256, ENCRYPTION_ECDSA_WITH_SHA256); + EC_ALGORITHMS.put(DIGEST_SHA384, ENCRYPTION_ECDSA_WITH_SHA384); + EC_ALGORITHMS.put(DIGEST_SHA512, ENCRYPTION_ECDSA_WITH_SHA512); + } + + protected List certs = new ArrayList(); + protected List crls = new ArrayList(); + protected List _signers = new ArrayList(); + protected List signerGens = new ArrayList(); + protected Map digests = new HashMap(); + + /** + * base constructor + */ + protected CMSSignedGenerator() + { + } + + protected Map getBaseParameters(ASN1ObjectIdentifier contentType, AlgorithmIdentifier digAlgId, byte[] hash) + { + Map param = new HashMap(); + param.put(CMSAttributeTableGenerator.CONTENT_TYPE, contentType); + param.put(CMSAttributeTableGenerator.DIGEST_ALGORITHM_IDENTIFIER, digAlgId); + param.put(CMSAttributeTableGenerator.DIGEST, Arrays.clone(hash)); + return param; + } + + /** + * Add a certificate to the certificate set to be included with the generated SignedData message. + * + * @param certificate the certificate to be included. + * @throws CMSException if the certificate cannot be encoded for adding. + */ + public void addCertificate( + X509CertificateHolder certificate) + throws CMSException + { + certs.add(certificate.toASN1Structure()); + } + + /** + * Add the certificates in certStore to the certificate set to be included with the generated SignedData message. + * + * @param certStore the store containing the certificates to be included. + * @throws CMSException if the certificates cannot be encoded for adding. + */ + public void addCertificates( + Store certStore) + throws CMSException + { + certs.addAll(CMSUtils.getCertificatesFromStore(certStore)); + } + + /** + * Add a CRL to the CRL set to be included with the generated SignedData message. + * + * @param crl the CRL to be included. + */ + public void addCRL(X509CRLHolder crl) + { + crls.add(crl.toASN1Structure()); + } + + /** + * Add the CRLs in crlStore to the CRL set to be included with the generated SignedData message. + * + * @param crlStore the store containing the CRLs to be included. + * @throws CMSException if the CRLs cannot be encoded for adding. + */ + public void addCRLs( + Store crlStore) + throws CMSException + { + crls.addAll(CMSUtils.getCRLsFromStore(crlStore)); + } + + /** + * Add the attribute certificates in attrStore to the certificate set to be included with the generated SignedData message. + * + * @param attrCert the store containing the certificates to be included. + * @throws CMSException if the attribute certificate cannot be encoded for adding. + */ + public void addAttributeCertificate( + X509AttributeCertificateHolder attrCert) + throws CMSException + { + certs.add(new DERTaggedObject(false, 2, attrCert.toASN1Structure())); + } + + /** + * Add the attribute certificates in attrStore to the certificate set to be included with the generated SignedData message. + * + * @param attrStore the store containing the certificates to be included. + * @throws CMSException if the attribute certificate cannot be encoded for adding. + */ + public void addAttributeCertificates( + Store attrStore) + throws CMSException + { + certs.addAll(CMSUtils.getAttributeCertificatesFromStore(attrStore)); + } + + /** + * Add a single instance of otherRevocationData to the CRL set to be included with the generated SignedData message. + * + * @param otherRevocationInfoFormat the OID specifying the format of the otherRevocationInfo data. + * @param otherRevocationInfo the otherRevocationInfo ASN.1 structure. + */ + public void addOtherRevocationInfo( + ASN1ObjectIdentifier otherRevocationInfoFormat, + ASN1Encodable otherRevocationInfo) + { + crls.add(new DERTaggedObject(false, 1, new OtherRevocationInfoFormat(otherRevocationInfoFormat, otherRevocationInfo))); + } + + /** + * Add a Store of otherRevocationData to the CRL set to be included with the generated SignedData message. + * + * @param otherRevocationInfoFormat the OID specifying the format of the otherRevocationInfo data. + * @param otherRevocationInfos a Store of otherRevocationInfo data to add. + */ + public void addOtherRevocationInfo( + ASN1ObjectIdentifier otherRevocationInfoFormat, + Store otherRevocationInfos) + { + crls.addAll(CMSUtils.getOthersFromStore(otherRevocationInfoFormat, otherRevocationInfos)); + } + + /** + * Add a store of pre-calculated signers to the generator. + * + * @param signerStore store of signers + */ + public void addSigners( + SignerInformationStore signerStore) + { + Iterator it = signerStore.getSigners().iterator(); + + while (it.hasNext()) + { + _signers.add(it.next()); + } + } + + /** + * Add a generator for a particular signer to this CMS SignedData generator. + * + * @param infoGen the generator representing the particular signer. + */ + public void addSignerInfoGenerator(SignerInfoGenerator infoGen) + { + signerGens.add(infoGen); + } + + /** + * Return a map of oids and byte arrays representing the digests calculated on the content during + * the last generate. + * + * @return a map of oids (as String objects) and byte[] representing digests. + */ + public Map getGeneratedDigests() + { + return new HashMap(digests); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSSignedHelper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSSignedHelper.java new file mode 100644 index 000000000..1f4187bc5 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSSignedHelper.java @@ -0,0 +1,253 @@ +package org.spongycastle.cms; + +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.ASN1Set; +import org.spongycastle.asn1.ASN1TaggedObject; +import org.spongycastle.asn1.DERNull; +import org.spongycastle.asn1.cms.OtherRevocationInfoFormat; +import org.spongycastle.asn1.cryptopro.CryptoProObjectIdentifiers; +import org.spongycastle.asn1.eac.EACObjectIdentifiers; +import org.spongycastle.asn1.nist.NISTObjectIdentifiers; +import org.spongycastle.asn1.oiw.OIWObjectIdentifiers; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.teletrust.TeleTrusTObjectIdentifiers; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.AttributeCertificate; +import org.spongycastle.asn1.x509.Certificate; +import org.spongycastle.asn1.x509.CertificateList; +import org.spongycastle.asn1.x509.X509ObjectIdentifiers; +import org.spongycastle.asn1.x9.X9ObjectIdentifiers; +import org.spongycastle.cert.X509AttributeCertificateHolder; +import org.spongycastle.cert.X509CRLHolder; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.util.CollectionStore; +import org.spongycastle.util.Store; + +class CMSSignedHelper +{ + static final CMSSignedHelper INSTANCE = new CMSSignedHelper(); + + private static final Map encryptionAlgs = new HashMap(); + private static final Map digestAlgs = new HashMap(); + private static final Map digestAliases = new HashMap(); + + private static void addEntries(ASN1ObjectIdentifier alias, String digest, String encryption) + { + digestAlgs.put(alias.getId(), digest); + encryptionAlgs.put(alias.getId(), encryption); + } + + static + { + addEntries(NISTObjectIdentifiers.dsa_with_sha224, "SHA224", "DSA"); + addEntries(NISTObjectIdentifiers.dsa_with_sha256, "SHA256", "DSA"); + addEntries(NISTObjectIdentifiers.dsa_with_sha384, "SHA384", "DSA"); + addEntries(NISTObjectIdentifiers.dsa_with_sha512, "SHA512", "DSA"); + addEntries(OIWObjectIdentifiers.dsaWithSHA1, "SHA1", "DSA"); + addEntries(OIWObjectIdentifiers.md4WithRSA, "MD4", "RSA"); + addEntries(OIWObjectIdentifiers.md4WithRSAEncryption, "MD4", "RSA"); + addEntries(OIWObjectIdentifiers.md5WithRSA, "MD5", "RSA"); + addEntries(OIWObjectIdentifiers.sha1WithRSA, "SHA1", "RSA"); + addEntries(PKCSObjectIdentifiers.md2WithRSAEncryption, "MD2", "RSA"); + addEntries(PKCSObjectIdentifiers.md4WithRSAEncryption, "MD4", "RSA"); + addEntries(PKCSObjectIdentifiers.md5WithRSAEncryption, "MD5", "RSA"); + addEntries(PKCSObjectIdentifiers.sha1WithRSAEncryption, "SHA1", "RSA"); + addEntries(PKCSObjectIdentifiers.sha224WithRSAEncryption, "SHA224", "RSA"); + addEntries(PKCSObjectIdentifiers.sha256WithRSAEncryption, "SHA256", "RSA"); + addEntries(PKCSObjectIdentifiers.sha384WithRSAEncryption, "SHA384", "RSA"); + addEntries(PKCSObjectIdentifiers.sha512WithRSAEncryption, "SHA512", "RSA"); + addEntries(X9ObjectIdentifiers.ecdsa_with_SHA1, "SHA1", "ECDSA"); + addEntries(X9ObjectIdentifiers.ecdsa_with_SHA224, "SHA224", "ECDSA"); + addEntries(X9ObjectIdentifiers.ecdsa_with_SHA256, "SHA256", "ECDSA"); + addEntries(X9ObjectIdentifiers.ecdsa_with_SHA384, "SHA384", "ECDSA"); + addEntries(X9ObjectIdentifiers.ecdsa_with_SHA512, "SHA512", "ECDSA"); + addEntries(X9ObjectIdentifiers.id_dsa_with_sha1, "SHA1", "DSA"); + addEntries(EACObjectIdentifiers.id_TA_ECDSA_SHA_1, "SHA1", "ECDSA"); + addEntries(EACObjectIdentifiers.id_TA_ECDSA_SHA_224, "SHA224", "ECDSA"); + addEntries(EACObjectIdentifiers.id_TA_ECDSA_SHA_256, "SHA256", "ECDSA"); + addEntries(EACObjectIdentifiers.id_TA_ECDSA_SHA_384, "SHA384", "ECDSA"); + addEntries(EACObjectIdentifiers.id_TA_ECDSA_SHA_512, "SHA512", "ECDSA"); + addEntries(EACObjectIdentifiers.id_TA_RSA_v1_5_SHA_1, "SHA1", "RSA"); + addEntries(EACObjectIdentifiers.id_TA_RSA_v1_5_SHA_256, "SHA256", "RSA"); + addEntries(EACObjectIdentifiers.id_TA_RSA_PSS_SHA_1, "SHA1", "RSAandMGF1"); + addEntries(EACObjectIdentifiers.id_TA_RSA_PSS_SHA_256, "SHA256", "RSAandMGF1"); + + encryptionAlgs.put(X9ObjectIdentifiers.id_dsa.getId(), "DSA"); + encryptionAlgs.put(PKCSObjectIdentifiers.rsaEncryption.getId(), "RSA"); + encryptionAlgs.put(TeleTrusTObjectIdentifiers.teleTrusTRSAsignatureAlgorithm, "RSA"); + encryptionAlgs.put(X509ObjectIdentifiers.id_ea_rsa.getId(), "RSA"); + encryptionAlgs.put(CMSSignedDataGenerator.ENCRYPTION_RSA_PSS, "RSAandMGF1"); + encryptionAlgs.put(CryptoProObjectIdentifiers.gostR3410_94.getId(), "GOST3410"); + encryptionAlgs.put(CryptoProObjectIdentifiers.gostR3410_2001.getId(), "ECGOST3410"); + encryptionAlgs.put("1.3.6.1.4.1.5849.1.6.2", "ECGOST3410"); + encryptionAlgs.put("1.3.6.1.4.1.5849.1.1.5", "GOST3410"); + encryptionAlgs.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_2001.getId(), "ECGOST3410"); + encryptionAlgs.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_94.getId(), "GOST3410"); + + digestAlgs.put(PKCSObjectIdentifiers.md2.getId(), "MD2"); + digestAlgs.put(PKCSObjectIdentifiers.md4.getId(), "MD4"); + digestAlgs.put(PKCSObjectIdentifiers.md5.getId(), "MD5"); + digestAlgs.put(OIWObjectIdentifiers.idSHA1.getId(), "SHA1"); + digestAlgs.put(NISTObjectIdentifiers.id_sha224.getId(), "SHA224"); + digestAlgs.put(NISTObjectIdentifiers.id_sha256.getId(), "SHA256"); + digestAlgs.put(NISTObjectIdentifiers.id_sha384.getId(), "SHA384"); + digestAlgs.put(NISTObjectIdentifiers.id_sha512.getId(), "SHA512"); + digestAlgs.put(TeleTrusTObjectIdentifiers.ripemd128.getId(), "RIPEMD128"); + digestAlgs.put(TeleTrusTObjectIdentifiers.ripemd160.getId(), "RIPEMD160"); + digestAlgs.put(TeleTrusTObjectIdentifiers.ripemd256.getId(), "RIPEMD256"); + digestAlgs.put(CryptoProObjectIdentifiers.gostR3411.getId(), "GOST3411"); + digestAlgs.put("1.3.6.1.4.1.5849.1.2.1", "GOST3411"); + + digestAliases.put("SHA1", new String[] { "SHA-1" }); + digestAliases.put("SHA224", new String[] { "SHA-224" }); + digestAliases.put("SHA256", new String[] { "SHA-256" }); + digestAliases.put("SHA384", new String[] { "SHA-384" }); + digestAliases.put("SHA512", new String[] { "SHA-512" }); + } + + + /** + * Return the digest encryption algorithm using one of the standard + * JCA string representations rather the the algorithm identifier (if + * possible). + */ + String getEncryptionAlgName( + String encryptionAlgOID) + { + String algName = (String)encryptionAlgs.get(encryptionAlgOID); + + if (algName != null) + { + return algName; + } + + return encryptionAlgOID; + } + + AlgorithmIdentifier fixAlgID(AlgorithmIdentifier algId) + { + if (algId.getParameters() == null) + { + return new AlgorithmIdentifier(algId.getAlgorithm(), DERNull.INSTANCE); + } + + return algId; + } + + void setSigningEncryptionAlgorithmMapping(ASN1ObjectIdentifier oid, String algorithmName) + { + encryptionAlgs.put(oid.getId(), algorithmName); + } + + void setSigningDigestAlgorithmMapping(ASN1ObjectIdentifier oid, String algorithmName) + { + digestAlgs.put(oid.getId(), algorithmName); + } + + Store getCertificates(ASN1Set certSet) + { + if (certSet != null) + { + List certList = new ArrayList(certSet.size()); + + for (Enumeration en = certSet.getObjects(); en.hasMoreElements();) + { + ASN1Primitive obj = ((ASN1Encodable)en.nextElement()).toASN1Primitive(); + + if (obj instanceof ASN1Sequence) + { + certList.add(new X509CertificateHolder(Certificate.getInstance(obj))); + } + } + + return new CollectionStore(certList); + } + + return new CollectionStore(new ArrayList()); + } + + Store getAttributeCertificates(ASN1Set certSet) + { + if (certSet != null) + { + List certList = new ArrayList(certSet.size()); + + for (Enumeration en = certSet.getObjects(); en.hasMoreElements();) + { + ASN1Primitive obj = ((ASN1Encodable)en.nextElement()).toASN1Primitive(); + + if (obj instanceof ASN1TaggedObject) + { + certList.add(new X509AttributeCertificateHolder(AttributeCertificate.getInstance(((ASN1TaggedObject)obj).getObject()))); + } + } + + return new CollectionStore(certList); + } + + return new CollectionStore(new ArrayList()); + } + + Store getCRLs(ASN1Set crlSet) + { + if (crlSet != null) + { + List crlList = new ArrayList(crlSet.size()); + + for (Enumeration en = crlSet.getObjects(); en.hasMoreElements();) + { + ASN1Primitive obj = ((ASN1Encodable)en.nextElement()).toASN1Primitive(); + + if (obj instanceof ASN1Sequence) + { + crlList.add(new X509CRLHolder(CertificateList.getInstance(obj))); + } + } + + return new CollectionStore(crlList); + } + + return new CollectionStore(new ArrayList()); + } + + Store getOtherRevocationInfo(ASN1ObjectIdentifier otherRevocationInfoFormat, ASN1Set crlSet) + { + if (crlSet != null) + { + List crlList = new ArrayList(crlSet.size()); + + for (Enumeration en = crlSet.getObjects(); en.hasMoreElements();) + { + ASN1Primitive obj = ((ASN1Encodable)en.nextElement()).toASN1Primitive(); + + if (obj instanceof ASN1TaggedObject) + { + ASN1TaggedObject tObj = ASN1TaggedObject.getInstance(obj); + + if (tObj.getTagNo() == 1) + { + OtherRevocationInfoFormat other = OtherRevocationInfoFormat.getInstance(tObj, false); + + if (otherRevocationInfoFormat.equals(other.getInfoFormat())) + { + crlList.add(other.getInfo()); + } + } + } + } + + return new CollectionStore(crlList); + } + + return new CollectionStore(new ArrayList()); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSSignerDigestMismatchException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSSignerDigestMismatchException.java new file mode 100644 index 000000000..c2bf8a264 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSSignerDigestMismatchException.java @@ -0,0 +1,11 @@ +package org.spongycastle.cms; + +public class CMSSignerDigestMismatchException + extends CMSException +{ + public CMSSignerDigestMismatchException( + String msg) + { + super(msg); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSStreamException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSStreamException.java new file mode 100644 index 000000000..655503539 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSStreamException.java @@ -0,0 +1,26 @@ +package org.spongycastle.cms; + +import java.io.IOException; + +public class CMSStreamException + extends IOException +{ + private final Throwable underlying; + + CMSStreamException(String msg) + { + super(msg); + this.underlying = null; + } + + CMSStreamException(String msg, Throwable underlying) + { + super(msg); + this.underlying = underlying; + } + + public Throwable getCause() + { + return underlying; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSTypedData.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSTypedData.java new file mode 100644 index 000000000..eb044d959 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSTypedData.java @@ -0,0 +1,9 @@ +package org.spongycastle.cms; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; + +public interface CMSTypedData + extends CMSProcessable +{ + ASN1ObjectIdentifier getContentType(); +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSTypedStream.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSTypedStream.java new file mode 100644 index 000000000..749844273 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSTypedStream.java @@ -0,0 +1,86 @@ +package org.spongycastle.cms; + +import java.io.BufferedInputStream; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.util.io.Streams; + +public class CMSTypedStream +{ + private static final int BUF_SIZ = 32 * 1024; + + private final ASN1ObjectIdentifier _oid; + private final InputStream _in; + + public CMSTypedStream( + InputStream in) + { + this(PKCSObjectIdentifiers.data.getId(), in, BUF_SIZ); + } + + public CMSTypedStream( + String oid, + InputStream in) + { + this(new ASN1ObjectIdentifier(oid), in, BUF_SIZ); + } + + public CMSTypedStream( + String oid, + InputStream in, + int bufSize) + { + this(new ASN1ObjectIdentifier(oid), in, bufSize); + } + + public CMSTypedStream( + ASN1ObjectIdentifier oid, + InputStream in) + { + this(oid, in, BUF_SIZ); + } + + public CMSTypedStream( + ASN1ObjectIdentifier oid, + InputStream in, + int bufSize) + { + _oid = oid; + _in = new FullReaderStream(new BufferedInputStream(in, bufSize)); + } + + public ASN1ObjectIdentifier getContentType() + { + return _oid; + } + + public InputStream getContentStream() + { + return _in; + } + + public void drain() + throws IOException + { + Streams.drain(_in); + _in.close(); + } + + private static class FullReaderStream extends FilterInputStream + { + FullReaderStream(InputStream in) + { + super(in); + } + + public int read(byte[] buf, int off, int len) throws IOException + { + int totalRead = Streams.readFully(super.in, buf, off, len); + return totalRead > 0 ? totalRead : -1; + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSUtils.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSUtils.java new file mode 100644 index 000000000..1294251c8 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSUtils.java @@ -0,0 +1,253 @@ +package org.spongycastle.cms; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1InputStream; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1Set; +import org.spongycastle.asn1.BEROctetStringGenerator; +import org.spongycastle.asn1.BERSet; +import org.spongycastle.asn1.DERSet; +import org.spongycastle.asn1.DERTaggedObject; +import org.spongycastle.asn1.cms.CMSObjectIdentifiers; +import org.spongycastle.asn1.cms.ContentInfo; +import org.spongycastle.asn1.cms.OtherRevocationInfoFormat; +import org.spongycastle.asn1.ocsp.OCSPResponse; +import org.spongycastle.asn1.ocsp.OCSPResponseStatus; +import org.spongycastle.cert.X509AttributeCertificateHolder; +import org.spongycastle.cert.X509CRLHolder; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.operator.DigestCalculator; +import org.spongycastle.util.Store; +import org.spongycastle.util.io.Streams; +import org.spongycastle.util.io.TeeInputStream; +import org.spongycastle.util.io.TeeOutputStream; + +class CMSUtils +{ + static ContentInfo readContentInfo( + byte[] input) + throws CMSException + { + // enforce limit checking as from a byte array + return readContentInfo(new ASN1InputStream(input)); + } + + static ContentInfo readContentInfo( + InputStream input) + throws CMSException + { + // enforce some limit checking + return readContentInfo(new ASN1InputStream(input)); + } + + static List getCertificatesFromStore(Store certStore) + throws CMSException + { + List certs = new ArrayList(); + + try + { + for (Iterator it = certStore.getMatches(null).iterator(); it.hasNext();) + { + X509CertificateHolder c = (X509CertificateHolder)it.next(); + + certs.add(c.toASN1Structure()); + } + + return certs; + } + catch (ClassCastException e) + { + throw new CMSException("error processing certs", e); + } + } + + static List getAttributeCertificatesFromStore(Store attrStore) + throws CMSException + { + List certs = new ArrayList(); + + try + { + for (Iterator it = attrStore.getMatches(null).iterator(); it.hasNext();) + { + X509AttributeCertificateHolder attrCert = (X509AttributeCertificateHolder)it.next(); + + certs.add(new DERTaggedObject(false, 2, attrCert.toASN1Structure())); + } + + return certs; + } + catch (ClassCastException e) + { + throw new CMSException("error processing certs", e); + } + } + + + static List getCRLsFromStore(Store crlStore) + throws CMSException + { + List certs = new ArrayList(); + + try + { + for (Iterator it = crlStore.getMatches(null).iterator(); it.hasNext();) + { + X509CRLHolder c = (X509CRLHolder)it.next(); + + certs.add(c.toASN1Structure()); + } + + return certs; + } + catch (ClassCastException e) + { + throw new CMSException("error processing certs", e); + } + } + + static Collection getOthersFromStore(ASN1ObjectIdentifier otherRevocationInfoFormat, Store otherRevocationInfos) + { + List others = new ArrayList(); + + for (Iterator it = otherRevocationInfos.getMatches(null).iterator(); it.hasNext();) + { + ASN1Encodable info = (ASN1Encodable)it.next(); + + if (CMSObjectIdentifiers.id_ri_ocsp_response.equals(otherRevocationInfoFormat)) + { + OCSPResponse resp = OCSPResponse.getInstance(info); + + if (resp.getResponseStatus().getValue().intValue() != OCSPResponseStatus.SUCCESSFUL) + { + throw new IllegalArgumentException("cannot add unsuccessful OCSP response to CMS SignedData"); + } + } + + others.add(new DERTaggedObject(false, 1, new OtherRevocationInfoFormat(otherRevocationInfoFormat, info))); + } + + return others; + } + + static ASN1Set createBerSetFromList(List derObjects) + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + for (Iterator it = derObjects.iterator(); it.hasNext();) + { + v.add((ASN1Encodable)it.next()); + } + + return new BERSet(v); + } + + static ASN1Set createDerSetFromList(List derObjects) + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + for (Iterator it = derObjects.iterator(); it.hasNext();) + { + v.add((ASN1Encodable)it.next()); + } + + return new DERSet(v); + } + + static OutputStream createBEROctetOutputStream(OutputStream s, + int tagNo, boolean isExplicit, int bufferSize) throws IOException + { + BEROctetStringGenerator octGen = new BEROctetStringGenerator(s, tagNo, isExplicit); + + if (bufferSize != 0) + { + return octGen.getOctetOutputStream(new byte[bufferSize]); + } + + return octGen.getOctetOutputStream(); + } + + private static ContentInfo readContentInfo( + ASN1InputStream in) + throws CMSException + { + try + { + return ContentInfo.getInstance(in.readObject()); + } + catch (IOException e) + { + throw new CMSException("IOException reading content.", e); + } + catch (ClassCastException e) + { + throw new CMSException("Malformed content.", e); + } + catch (IllegalArgumentException e) + { + throw new CMSException("Malformed content.", e); + } + } + + public static byte[] streamToByteArray( + InputStream in) + throws IOException + { + return Streams.readAll(in); + } + + public static byte[] streamToByteArray( + InputStream in, + int limit) + throws IOException + { + return Streams.readAllLimited(in, limit); + } + + static InputStream attachDigestsToInputStream(Collection digests, InputStream s) + { + InputStream result = s; + Iterator it = digests.iterator(); + while (it.hasNext()) + { + DigestCalculator digest = (DigestCalculator)it.next(); + result = new TeeInputStream(result, digest.getOutputStream()); + } + return result; + } + + static OutputStream attachSignersToOutputStream(Collection signers, OutputStream s) + { + OutputStream result = s; + Iterator it = signers.iterator(); + while (it.hasNext()) + { + SignerInfoGenerator signerGen = (SignerInfoGenerator)it.next(); + result = getSafeTeeOutputStream(result, signerGen.getCalculatingOutputStream()); + } + return result; + } + + static OutputStream getSafeOutputStream(OutputStream s) + { + return s == null ? new NullOutputStream() : s; + } + + static OutputStream getSafeTeeOutputStream(OutputStream s1, + OutputStream s2) + { + return s1 == null ? getSafeOutputStream(s2) + : s2 == null ? getSafeOutputStream(s1) : new TeeOutputStream( + s1, s2); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSVerifierCertificateNotValidException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSVerifierCertificateNotValidException.java new file mode 100644 index 000000000..bdcf131b4 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/CMSVerifierCertificateNotValidException.java @@ -0,0 +1,11 @@ +package org.spongycastle.cms; + +public class CMSVerifierCertificateNotValidException + extends CMSException +{ + public CMSVerifierCertificateNotValidException( + String msg) + { + super(msg); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/DefaultAuthenticatedAttributeTableGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/DefaultAuthenticatedAttributeTableGenerator.java new file mode 100644 index 000000000..3fa4cf702 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/DefaultAuthenticatedAttributeTableGenerator.java @@ -0,0 +1,91 @@ +package org.spongycastle.cms; + +import java.util.Hashtable; +import java.util.Map; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.DERSet; +import org.spongycastle.asn1.cms.Attribute; +import org.spongycastle.asn1.cms.AttributeTable; +import org.spongycastle.asn1.cms.CMSAttributes; + +/** + * Default authenticated attributes generator. + */ +public class DefaultAuthenticatedAttributeTableGenerator + implements CMSAttributeTableGenerator +{ + private final Hashtable table; + + /** + * Initialise to use all defaults + */ + public DefaultAuthenticatedAttributeTableGenerator() + { + table = new Hashtable(); + } + + /** + * Initialise with some extra attributes or overrides. + * + * @param attributeTable initial attribute table to use. + */ + public DefaultAuthenticatedAttributeTableGenerator( + AttributeTable attributeTable) + { + if (attributeTable != null) + { + table = attributeTable.toHashtable(); + } + else + { + table = new Hashtable(); + } + } + + /** + * Create a standard attribute table from the passed in parameters - this will + * normally include contentType and messageDigest. If the constructor + * using an AttributeTable was used, entries in it for contentType and + * messageDigest will override the generated ones. + * + * @param parameters source parameters for table generation. + * + * @return a filled in Hashtable of attributes. + */ + protected Hashtable createStandardAttributeTable( + Map parameters) + { + Hashtable std = (Hashtable)table.clone(); + + if (!std.containsKey(CMSAttributes.contentType)) + { + ASN1ObjectIdentifier contentType = ASN1ObjectIdentifier.getInstance( + parameters.get(CMSAttributeTableGenerator.CONTENT_TYPE)); + Attribute attr = new Attribute(CMSAttributes.contentType, + new DERSet(contentType)); + std.put(attr.getAttrType(), attr); + } + + if (!std.containsKey(CMSAttributes.messageDigest)) + { + byte[] messageDigest = (byte[])parameters.get( + CMSAttributeTableGenerator.DIGEST); + Attribute attr = new Attribute(CMSAttributes.messageDigest, + new DERSet(new DEROctetString(messageDigest))); + std.put(attr.getAttrType(), attr); + } + + return std; + } + + /** + * @param parameters source parameters + * @return the populated attribute table + */ + public AttributeTable getAttributes(Map parameters) + { + return new AttributeTable(createStandardAttributeTable(parameters)); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/DefaultCMSSignatureAlgorithmNameGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/DefaultCMSSignatureAlgorithmNameGenerator.java new file mode 100644 index 000000000..9a4f09226 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/DefaultCMSSignatureAlgorithmNameGenerator.java @@ -0,0 +1,154 @@ +package org.spongycastle.cms; + +import java.util.HashMap; +import java.util.Map; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.cryptopro.CryptoProObjectIdentifiers; +import org.spongycastle.asn1.eac.EACObjectIdentifiers; +import org.spongycastle.asn1.nist.NISTObjectIdentifiers; +import org.spongycastle.asn1.oiw.OIWObjectIdentifiers; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.teletrust.TeleTrusTObjectIdentifiers; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.X509ObjectIdentifiers; +import org.spongycastle.asn1.x9.X9ObjectIdentifiers; + +public class DefaultCMSSignatureAlgorithmNameGenerator + implements CMSSignatureAlgorithmNameGenerator +{ + private final Map encryptionAlgs = new HashMap(); + private final Map digestAlgs = new HashMap(); + + private void addEntries(ASN1ObjectIdentifier alias, String digest, String encryption) + { + digestAlgs.put(alias, digest); + encryptionAlgs.put(alias, encryption); + } + + public DefaultCMSSignatureAlgorithmNameGenerator() + { + addEntries(NISTObjectIdentifiers.dsa_with_sha224, "SHA224", "DSA"); + addEntries(NISTObjectIdentifiers.dsa_with_sha256, "SHA256", "DSA"); + addEntries(NISTObjectIdentifiers.dsa_with_sha384, "SHA384", "DSA"); + addEntries(NISTObjectIdentifiers.dsa_with_sha512, "SHA512", "DSA"); + addEntries(OIWObjectIdentifiers.dsaWithSHA1, "SHA1", "DSA"); + addEntries(OIWObjectIdentifiers.md4WithRSA, "MD4", "RSA"); + addEntries(OIWObjectIdentifiers.md4WithRSAEncryption, "MD4", "RSA"); + addEntries(OIWObjectIdentifiers.md5WithRSA, "MD5", "RSA"); + addEntries(OIWObjectIdentifiers.sha1WithRSA, "SHA1", "RSA"); + addEntries(PKCSObjectIdentifiers.md2WithRSAEncryption, "MD2", "RSA"); + addEntries(PKCSObjectIdentifiers.md4WithRSAEncryption, "MD4", "RSA"); + addEntries(PKCSObjectIdentifiers.md5WithRSAEncryption, "MD5", "RSA"); + addEntries(PKCSObjectIdentifiers.sha1WithRSAEncryption, "SHA1", "RSA"); + addEntries(PKCSObjectIdentifiers.sha224WithRSAEncryption, "SHA224", "RSA"); + addEntries(PKCSObjectIdentifiers.sha256WithRSAEncryption, "SHA256", "RSA"); + addEntries(PKCSObjectIdentifiers.sha384WithRSAEncryption, "SHA384", "RSA"); + addEntries(PKCSObjectIdentifiers.sha512WithRSAEncryption, "SHA512", "RSA"); + addEntries(X9ObjectIdentifiers.ecdsa_with_SHA1, "SHA1", "ECDSA"); + addEntries(X9ObjectIdentifiers.ecdsa_with_SHA224, "SHA224", "ECDSA"); + addEntries(X9ObjectIdentifiers.ecdsa_with_SHA256, "SHA256", "ECDSA"); + addEntries(X9ObjectIdentifiers.ecdsa_with_SHA384, "SHA384", "ECDSA"); + addEntries(X9ObjectIdentifiers.ecdsa_with_SHA512, "SHA512", "ECDSA"); + addEntries(X9ObjectIdentifiers.id_dsa_with_sha1, "SHA1", "DSA"); + addEntries(EACObjectIdentifiers.id_TA_ECDSA_SHA_1, "SHA1", "ECDSA"); + addEntries(EACObjectIdentifiers.id_TA_ECDSA_SHA_224, "SHA224", "ECDSA"); + addEntries(EACObjectIdentifiers.id_TA_ECDSA_SHA_256, "SHA256", "ECDSA"); + addEntries(EACObjectIdentifiers.id_TA_ECDSA_SHA_384, "SHA384", "ECDSA"); + addEntries(EACObjectIdentifiers.id_TA_ECDSA_SHA_512, "SHA512", "ECDSA"); + addEntries(EACObjectIdentifiers.id_TA_RSA_v1_5_SHA_1, "SHA1", "RSA"); + addEntries(EACObjectIdentifiers.id_TA_RSA_v1_5_SHA_256, "SHA256", "RSA"); + addEntries(EACObjectIdentifiers.id_TA_RSA_PSS_SHA_1, "SHA1", "RSAandMGF1"); + addEntries(EACObjectIdentifiers.id_TA_RSA_PSS_SHA_256, "SHA256", "RSAandMGF1"); + + encryptionAlgs.put(X9ObjectIdentifiers.id_dsa, "DSA"); + encryptionAlgs.put(PKCSObjectIdentifiers.rsaEncryption, "RSA"); + encryptionAlgs.put(TeleTrusTObjectIdentifiers.teleTrusTRSAsignatureAlgorithm, "RSA"); + encryptionAlgs.put(X509ObjectIdentifiers.id_ea_rsa, "RSA"); + encryptionAlgs.put(PKCSObjectIdentifiers.id_RSASSA_PSS, "RSAandMGF1"); + encryptionAlgs.put(CryptoProObjectIdentifiers.gostR3410_94, "GOST3410"); + encryptionAlgs.put(CryptoProObjectIdentifiers.gostR3410_2001, "ECGOST3410"); + encryptionAlgs.put(new ASN1ObjectIdentifier("1.3.6.1.4.1.5849.1.6.2"), "ECGOST3410"); + encryptionAlgs.put(new ASN1ObjectIdentifier("1.3.6.1.4.1.5849.1.1.5"), "GOST3410"); + encryptionAlgs.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_2001, "ECGOST3410"); + encryptionAlgs.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_94, "GOST3410"); + + digestAlgs.put(PKCSObjectIdentifiers.md2, "MD2"); + digestAlgs.put(PKCSObjectIdentifiers.md4, "MD4"); + digestAlgs.put(PKCSObjectIdentifiers.md5, "MD5"); + digestAlgs.put(OIWObjectIdentifiers.idSHA1, "SHA1"); + digestAlgs.put(NISTObjectIdentifiers.id_sha224, "SHA224"); + digestAlgs.put(NISTObjectIdentifiers.id_sha256, "SHA256"); + digestAlgs.put(NISTObjectIdentifiers.id_sha384, "SHA384"); + digestAlgs.put(NISTObjectIdentifiers.id_sha512, "SHA512"); + digestAlgs.put(TeleTrusTObjectIdentifiers.ripemd128, "RIPEMD128"); + digestAlgs.put(TeleTrusTObjectIdentifiers.ripemd160, "RIPEMD160"); + digestAlgs.put(TeleTrusTObjectIdentifiers.ripemd256, "RIPEMD256"); + digestAlgs.put(CryptoProObjectIdentifiers.gostR3411, "GOST3411"); + digestAlgs.put(new ASN1ObjectIdentifier("1.3.6.1.4.1.5849.1.2.1"), "GOST3411"); + } + + /** + * Return the digest algorithm using one of the standard JCA string + * representations rather than the algorithm identifier (if possible). + */ + private String getDigestAlgName( + ASN1ObjectIdentifier digestAlgOID) + { + String algName = (String)digestAlgs.get(digestAlgOID); + + if (algName != null) + { + return algName; + } + + return digestAlgOID.getId(); + } + + /** + * Return the digest encryption algorithm using one of the standard + * JCA string representations rather the the algorithm identifier (if + * possible). + */ + private String getEncryptionAlgName( + ASN1ObjectIdentifier encryptionAlgOID) + { + String algName = (String)encryptionAlgs.get(encryptionAlgOID); + + if (algName != null) + { + return algName; + } + + return encryptionAlgOID.getId(); + } + + /** + * Set the mapping for the encryption algorithm used in association with a SignedData generation + * or interpretation. + * + * @param oid object identifier to map. + * @param algorithmName algorithm name to use. + */ + protected void setSigningEncryptionAlgorithmMapping(ASN1ObjectIdentifier oid, String algorithmName) + { + encryptionAlgs.put(oid, algorithmName); + } + + /** + * Set the mapping for the digest algorithm to use in conjunction with a SignedData generation + * or interpretation. + * + * @param oid object identifier to map. + * @param algorithmName algorithm name to use. + */ + protected void setSigningDigestAlgorithmMapping(ASN1ObjectIdentifier oid, String algorithmName) + { + digestAlgs.put(oid, algorithmName); + } + + public String getSignatureName(AlgorithmIdentifier digestAlg, AlgorithmIdentifier encryptionAlg) + { + return getDigestAlgName(digestAlg.getAlgorithm()) + "with" + getEncryptionAlgName(encryptionAlg.getAlgorithm()); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/DefaultCMSSignatureEncryptionAlgorithmFinder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/DefaultCMSSignatureEncryptionAlgorithmFinder.java new file mode 100644 index 000000000..4608bb108 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/DefaultCMSSignatureEncryptionAlgorithmFinder.java @@ -0,0 +1,46 @@ +package org.spongycastle.cms; + +import java.util.HashSet; +import java.util.Set; + +import org.spongycastle.asn1.DERNull; +import org.spongycastle.asn1.oiw.OIWObjectIdentifiers; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.teletrust.TeleTrusTObjectIdentifiers; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +public class DefaultCMSSignatureEncryptionAlgorithmFinder + implements CMSSignatureEncryptionAlgorithmFinder +{ + private static final Set RSA_PKCS1d5 = new HashSet(); + + static + { + RSA_PKCS1d5.add(PKCSObjectIdentifiers.md2WithRSAEncryption); + RSA_PKCS1d5.add(PKCSObjectIdentifiers.md4WithRSAEncryption); + RSA_PKCS1d5.add(PKCSObjectIdentifiers.md5WithRSAEncryption); + RSA_PKCS1d5.add(PKCSObjectIdentifiers.sha1WithRSAEncryption); + RSA_PKCS1d5.add(PKCSObjectIdentifiers.sha224WithRSAEncryption); + RSA_PKCS1d5.add(PKCSObjectIdentifiers.sha256WithRSAEncryption); + RSA_PKCS1d5.add(PKCSObjectIdentifiers.sha384WithRSAEncryption); + RSA_PKCS1d5.add(PKCSObjectIdentifiers.sha512WithRSAEncryption); + RSA_PKCS1d5.add(OIWObjectIdentifiers.md4WithRSAEncryption); + RSA_PKCS1d5.add(OIWObjectIdentifiers.md4WithRSA); + RSA_PKCS1d5.add(OIWObjectIdentifiers.md5WithRSA); + RSA_PKCS1d5.add(OIWObjectIdentifiers.sha1WithRSA); + RSA_PKCS1d5.add(TeleTrusTObjectIdentifiers.rsaSignatureWithripemd128); + RSA_PKCS1d5.add(TeleTrusTObjectIdentifiers.rsaSignatureWithripemd160); + RSA_PKCS1d5.add(TeleTrusTObjectIdentifiers.rsaSignatureWithripemd256); + } + + public AlgorithmIdentifier findEncryptionAlgorithm(AlgorithmIdentifier signatureAlgorithm) + { + // RFC3370 section 3.2 + if (RSA_PKCS1d5.contains(signatureAlgorithm.getAlgorithm())) + { + return new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE); + } + + return signatureAlgorithm; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/DefaultSignedAttributeTableGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/DefaultSignedAttributeTableGenerator.java new file mode 100644 index 000000000..ba570c288 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/DefaultSignedAttributeTableGenerator.java @@ -0,0 +1,121 @@ +package org.spongycastle.cms; + +import java.util.Date; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Map; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.DERSet; +import org.spongycastle.asn1.cms.Attribute; +import org.spongycastle.asn1.cms.AttributeTable; +import org.spongycastle.asn1.cms.CMSAttributes; +import org.spongycastle.asn1.cms.Time; + +/** + * Default signed attributes generator. + */ +public class DefaultSignedAttributeTableGenerator + implements CMSAttributeTableGenerator +{ + private final Hashtable table; + + /** + * Initialise to use all defaults + */ + public DefaultSignedAttributeTableGenerator() + { + table = new Hashtable(); + } + + /** + * Initialise with some extra attributes or overrides. + * + * @param attributeTable initial attribute table to use. + */ + public DefaultSignedAttributeTableGenerator( + AttributeTable attributeTable) + { + if (attributeTable != null) + { + table = attributeTable.toHashtable(); + } + else + { + table = new Hashtable(); + } + } + + /** + * Create a standard attribute table from the passed in parameters - this will + * normally include contentType, signingTime, and messageDigest. If the constructor + * using an AttributeTable was used, entries in it for contentType, signingTime, and + * messageDigest will override the generated ones. + * + * @param parameters source parameters for table generation. + * + * @return a filled in Hashtable of attributes. + */ + protected Hashtable createStandardAttributeTable( + Map parameters) + { + Hashtable std = copyHashTable(table); + + if (!std.containsKey(CMSAttributes.contentType)) + { + ASN1ObjectIdentifier contentType = ASN1ObjectIdentifier.getInstance( + parameters.get(CMSAttributeTableGenerator.CONTENT_TYPE)); + + // contentType will be null if we're trying to generate a counter signature. + if (contentType != null) + { + Attribute attr = new Attribute(CMSAttributes.contentType, + new DERSet(contentType)); + std.put(attr.getAttrType(), attr); + } + } + + if (!std.containsKey(CMSAttributes.signingTime)) + { + Date signingTime = new Date(); + Attribute attr = new Attribute(CMSAttributes.signingTime, + new DERSet(new Time(signingTime))); + std.put(attr.getAttrType(), attr); + } + + if (!std.containsKey(CMSAttributes.messageDigest)) + { + byte[] messageDigest = (byte[])parameters.get( + CMSAttributeTableGenerator.DIGEST); + Attribute attr = new Attribute(CMSAttributes.messageDigest, + new DERSet(new DEROctetString(messageDigest))); + std.put(attr.getAttrType(), attr); + } + + return std; + } + + /** + * @param parameters source parameters + * @return the populated attribute table + */ + public AttributeTable getAttributes(Map parameters) + { + return new AttributeTable(createStandardAttributeTable(parameters)); + } + + private static Hashtable copyHashTable(Hashtable paramsMap) + { + Hashtable newTable = new Hashtable(); + + Enumeration keys = paramsMap.keys(); + while (keys.hasMoreElements()) + { + Object key = keys.nextElement(); + newTable.put(key, paramsMap.get(key)); + } + + return newTable; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KEKRecipient.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KEKRecipient.java new file mode 100644 index 000000000..3b14a89dc --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KEKRecipient.java @@ -0,0 +1,10 @@ +package org.spongycastle.cms; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +public interface KEKRecipient + extends Recipient +{ + RecipientOperator getRecipientOperator(AlgorithmIdentifier keyEncAlg, AlgorithmIdentifier contentEncryptionAlgorithm, byte[] encryptedContentKey) + throws CMSException; +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KEKRecipientId.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KEKRecipientId.java new file mode 100644 index 000000000..2025c7c16 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KEKRecipientId.java @@ -0,0 +1,63 @@ +package org.spongycastle.cms; + +import org.spongycastle.util.Arrays; + +public class KEKRecipientId + extends RecipientId +{ + private byte[] keyIdentifier; + + /** + * Construct a recipient ID with the key identifier of a KEK recipient. + * + * @param keyIdentifier a subjectKeyId + */ + public KEKRecipientId(byte[] keyIdentifier) + { + super(kek); + + this.keyIdentifier = keyIdentifier; + } + + public int hashCode() + { + return Arrays.hashCode(keyIdentifier); + } + + public boolean equals( + Object o) + { + if (!(o instanceof KEKRecipientId)) + { + return false; + } + + KEKRecipientId id = (KEKRecipientId)o; + + return Arrays.areEqual(keyIdentifier, id.keyIdentifier); + } + + public byte[] getKeyIdentifier() + { + return Arrays.clone(keyIdentifier); + } + + public Object clone() + { + return new KEKRecipientId(keyIdentifier); + } + + public boolean match(Object obj) + { + if (obj instanceof byte[]) + { + return Arrays.areEqual(keyIdentifier, (byte[])obj); + } + else if (obj instanceof KEKRecipientInformation) + { + return ((KEKRecipientInformation)obj).getRID().equals(this); + } + + return false; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KEKRecipientInfoGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KEKRecipientInfoGenerator.java new file mode 100644 index 000000000..480fb5fe9 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KEKRecipientInfoGenerator.java @@ -0,0 +1,39 @@ +package org.spongycastle.cms; + +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.cms.KEKIdentifier; +import org.spongycastle.asn1.cms.KEKRecipientInfo; +import org.spongycastle.asn1.cms.RecipientInfo; +import org.spongycastle.operator.GenericKey; +import org.spongycastle.operator.OperatorException; +import org.spongycastle.operator.SymmetricKeyWrapper; + +public abstract class KEKRecipientInfoGenerator + implements RecipientInfoGenerator +{ + private final KEKIdentifier kekIdentifier; + + protected final SymmetricKeyWrapper wrapper; + + protected KEKRecipientInfoGenerator(KEKIdentifier kekIdentifier, SymmetricKeyWrapper wrapper) + { + this.kekIdentifier = kekIdentifier; + this.wrapper = wrapper; + } + + public final RecipientInfo generate(GenericKey contentEncryptionKey) + throws CMSException + { + try + { + ASN1OctetString encryptedKey = new DEROctetString(wrapper.generateWrappedKey(contentEncryptionKey)); + + return new RecipientInfo(new KEKRecipientInfo(kekIdentifier, wrapper.getAlgorithmIdentifier(), encryptedKey)); + } + catch (OperatorException e) + { + throw new CMSException("exception wrapping content key: " + e.getMessage(), e); + } + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KEKRecipientInformation.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KEKRecipientInformation.java new file mode 100644 index 000000000..3388d6fa6 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KEKRecipientInformation.java @@ -0,0 +1,38 @@ +package org.spongycastle.cms; + +import java.io.IOException; + +import org.spongycastle.asn1.cms.KEKIdentifier; +import org.spongycastle.asn1.cms.KEKRecipientInfo; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +/** + * the RecipientInfo class for a recipient who has been sent a message + * encrypted using a secret key known to the other side. + */ +public class KEKRecipientInformation + extends RecipientInformation +{ + private KEKRecipientInfo info; + + KEKRecipientInformation( + KEKRecipientInfo info, + AlgorithmIdentifier messageAlgorithm, + CMSSecureReadable secureReadable, + AuthAttributesProvider additionalData) + { + super(info.getKeyEncryptionAlgorithm(), messageAlgorithm, secureReadable, additionalData); + + this.info = info; + + KEKIdentifier kekId = info.getKekid(); + + this.rid = new KEKRecipientId(kekId.getKeyIdentifier().getOctets()); + } + + protected RecipientOperator getRecipientOperator(Recipient recipient) + throws CMSException, IOException + { + return ((KEKRecipient)recipient).getRecipientOperator(keyEncAlg, messageAlgorithm, info.getEncryptedKey().getOctets()); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KeyAgreeRecipient.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KeyAgreeRecipient.java new file mode 100644 index 000000000..ab3a7451d --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KeyAgreeRecipient.java @@ -0,0 +1,14 @@ +package org.spongycastle.cms; + +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; + +public interface KeyAgreeRecipient + extends Recipient +{ + RecipientOperator getRecipientOperator(AlgorithmIdentifier keyEncAlg, AlgorithmIdentifier contentEncryptionAlgorithm, SubjectPublicKeyInfo senderPublicKey, ASN1OctetString userKeyingMaterial, byte[] encryptedContentKey) + throws CMSException; + + AlgorithmIdentifier getPrivateKeyAlgorithmIdentifier(); +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KeyAgreeRecipientId.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KeyAgreeRecipientId.java new file mode 100644 index 000000000..c5f2aa14e --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KeyAgreeRecipientId.java @@ -0,0 +1,89 @@ +package org.spongycastle.cms; + +import java.math.BigInteger; + +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.cert.selector.X509CertificateHolderSelector; + +public class KeyAgreeRecipientId + extends RecipientId +{ + private X509CertificateHolderSelector baseSelector; + + private KeyAgreeRecipientId(X509CertificateHolderSelector baseSelector) + { + super(keyAgree); + + this.baseSelector = baseSelector; + } + + /** + * Construct a key agree recipient ID with the value of a public key's subjectKeyId. + * + * @param subjectKeyId a subjectKeyId + */ + public KeyAgreeRecipientId(byte[] subjectKeyId) + { + this(null, null, subjectKeyId); + } + + /** + * Construct a key agree recipient ID based on the issuer and serial number of the recipient's associated + * certificate. + * + * @param issuer the issuer of the recipient's associated certificate. + * @param serialNumber the serial number of the recipient's associated certificate. + */ + public KeyAgreeRecipientId(X500Name issuer, BigInteger serialNumber) + { + this(issuer, serialNumber, null); + } + + public KeyAgreeRecipientId(X500Name issuer, BigInteger serialNumber, byte[] subjectKeyId) + { + this(new X509CertificateHolderSelector(issuer, serialNumber, subjectKeyId)); + } + + public BigInteger getSerialNumber() + { + return baseSelector.getSerialNumber(); + } + + public byte[] getSubjectKeyIdentifier() + { + return baseSelector.getSubjectKeyIdentifier(); + } + + public int hashCode() + { + return baseSelector.hashCode(); + } + + public boolean equals( + Object o) + { + if (!(o instanceof KeyAgreeRecipientId)) + { + return false; + } + + KeyAgreeRecipientId id = (KeyAgreeRecipientId)o; + + return this.baseSelector.equals(id.baseSelector); + } + + public Object clone() + { + return new KeyAgreeRecipientId(baseSelector); + } + + public boolean match(Object obj) + { + if (obj instanceof KeyAgreeRecipientInformation) + { + return ((KeyAgreeRecipientInformation)obj).getRID().equals(this); + } + + return baseSelector.match(obj); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KeyAgreeRecipientInfoGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KeyAgreeRecipientInfoGenerator.java new file mode 100644 index 000000000..b80ec3751 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KeyAgreeRecipientInfoGenerator.java @@ -0,0 +1,80 @@ +package org.spongycastle.cms; + +import java.io.IOException; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.DERNull; +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.cms.KeyAgreeRecipientInfo; +import org.spongycastle.asn1.cms.OriginatorIdentifierOrKey; +import org.spongycastle.asn1.cms.OriginatorPublicKey; +import org.spongycastle.asn1.cms.RecipientInfo; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.operator.GenericKey; + +public abstract class KeyAgreeRecipientInfoGenerator + implements RecipientInfoGenerator +{ + private ASN1ObjectIdentifier keyAgreementOID; + private ASN1ObjectIdentifier keyEncryptionOID; + private SubjectPublicKeyInfo originatorKeyInfo; + + protected KeyAgreeRecipientInfoGenerator(ASN1ObjectIdentifier keyAgreementOID, SubjectPublicKeyInfo originatorKeyInfo, ASN1ObjectIdentifier keyEncryptionOID) + { + this.originatorKeyInfo = originatorKeyInfo; + this.keyAgreementOID = keyAgreementOID; + this.keyEncryptionOID = keyEncryptionOID; + } + + public RecipientInfo generate(GenericKey contentEncryptionKey) + throws CMSException + { + OriginatorIdentifierOrKey originator = new OriginatorIdentifierOrKey( + createOriginatorPublicKey(originatorKeyInfo)); + + ASN1EncodableVector params = new ASN1EncodableVector(); + params.add(keyEncryptionOID); + params.add(DERNull.INSTANCE); + AlgorithmIdentifier keyEncAlg = new AlgorithmIdentifier(keyEncryptionOID, DERNull.INSTANCE); + AlgorithmIdentifier keyAgreeAlg = new AlgorithmIdentifier(keyAgreementOID, keyEncAlg); + + ASN1Sequence recipients = generateRecipientEncryptedKeys(keyAgreeAlg, keyEncAlg, contentEncryptionKey); + ASN1Encodable userKeyingMaterial = getUserKeyingMaterial(keyAgreeAlg); + + if (userKeyingMaterial != null) + { + try + { + return new RecipientInfo(new KeyAgreeRecipientInfo(originator, new DEROctetString(userKeyingMaterial), + keyAgreeAlg, recipients)); + } + catch (IOException e) + { + throw new CMSException("unable to encode userKeyingMaterial: " + e.getMessage(), e); + } + } + else + { + return new RecipientInfo(new KeyAgreeRecipientInfo(originator, null, + keyAgreeAlg, recipients)); + } + } + + protected OriginatorPublicKey createOriginatorPublicKey(SubjectPublicKeyInfo originatorKeyInfo) + { + return new OriginatorPublicKey( + new AlgorithmIdentifier(originatorKeyInfo.getAlgorithm().getAlgorithm(), DERNull.INSTANCE), + originatorKeyInfo.getPublicKeyData().getBytes()); + } + + protected abstract ASN1Sequence generateRecipientEncryptedKeys(AlgorithmIdentifier keyAgreeAlgorithm, AlgorithmIdentifier keyEncAlgorithm, GenericKey contentEncryptionKey) + throws CMSException; + + protected abstract ASN1Encodable getUserKeyingMaterial(AlgorithmIdentifier keyAgreeAlgorithm) + throws CMSException; + +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KeyAgreeRecipientInformation.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KeyAgreeRecipientInformation.java new file mode 100644 index 000000000..c00dcdf32 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KeyAgreeRecipientInformation.java @@ -0,0 +1,131 @@ +package org.spongycastle.cms; + +import java.io.IOException; +import java.util.List; + +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.cms.IssuerAndSerialNumber; +import org.spongycastle.asn1.cms.KeyAgreeRecipientIdentifier; +import org.spongycastle.asn1.cms.KeyAgreeRecipientInfo; +import org.spongycastle.asn1.cms.OriginatorIdentifierOrKey; +import org.spongycastle.asn1.cms.OriginatorPublicKey; +import org.spongycastle.asn1.cms.RecipientEncryptedKey; +import org.spongycastle.asn1.cms.RecipientKeyIdentifier; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.SubjectKeyIdentifier; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; + +/** + * the RecipientInfo class for a recipient who has been sent a message + * encrypted using key agreement. + */ +public class KeyAgreeRecipientInformation + extends RecipientInformation +{ + private KeyAgreeRecipientInfo info; + private ASN1OctetString encryptedKey; + + static void readRecipientInfo(List infos, KeyAgreeRecipientInfo info, + AlgorithmIdentifier messageAlgorithm, CMSSecureReadable secureReadable, AuthAttributesProvider additionalData) + { + ASN1Sequence s = info.getRecipientEncryptedKeys(); + + for (int i = 0; i < s.size(); ++i) + { + RecipientEncryptedKey id = RecipientEncryptedKey.getInstance( + s.getObjectAt(i)); + + RecipientId rid; + + KeyAgreeRecipientIdentifier karid = id.getIdentifier(); + IssuerAndSerialNumber iAndSN = karid.getIssuerAndSerialNumber(); + + if (iAndSN != null) + { + rid = new KeyAgreeRecipientId(iAndSN.getName(), iAndSN.getSerialNumber().getValue()); + } + else + { + RecipientKeyIdentifier rKeyID = karid.getRKeyID(); + + // Note: 'date' and 'other' fields of RecipientKeyIdentifier appear to be only informational + + rid = new KeyAgreeRecipientId(rKeyID.getSubjectKeyIdentifier().getOctets()); + } + + infos.add(new KeyAgreeRecipientInformation(info, rid, id.getEncryptedKey(), messageAlgorithm, + secureReadable, additionalData)); + } + } + + KeyAgreeRecipientInformation( + KeyAgreeRecipientInfo info, + RecipientId rid, + ASN1OctetString encryptedKey, + AlgorithmIdentifier messageAlgorithm, + CMSSecureReadable secureReadable, + AuthAttributesProvider additionalData) + { + super(info.getKeyEncryptionAlgorithm(), messageAlgorithm, secureReadable, additionalData); + + this.info = info; + this.rid = rid; + this.encryptedKey = encryptedKey; + } + + private SubjectPublicKeyInfo getSenderPublicKeyInfo(AlgorithmIdentifier recKeyAlgId, + OriginatorIdentifierOrKey originator) + throws CMSException, IOException + { + OriginatorPublicKey opk = originator.getOriginatorKey(); + if (opk != null) + { + return getPublicKeyInfoFromOriginatorPublicKey(recKeyAlgId, opk); + } + + OriginatorId origID; + + IssuerAndSerialNumber iAndSN = originator.getIssuerAndSerialNumber(); + if (iAndSN != null) + { + origID = new OriginatorId(iAndSN.getName(), iAndSN.getSerialNumber().getValue()); + } + else + { + SubjectKeyIdentifier ski = originator.getSubjectKeyIdentifier(); + + origID = new OriginatorId(ski.getKeyIdentifier()); + } + + return getPublicKeyInfoFromOriginatorId(origID); + } + + private SubjectPublicKeyInfo getPublicKeyInfoFromOriginatorPublicKey(AlgorithmIdentifier recKeyAlgId, + OriginatorPublicKey originatorPublicKey) + { + SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo( + recKeyAlgId, + originatorPublicKey.getPublicKey().getBytes()); + + return pubInfo; + } + + private SubjectPublicKeyInfo getPublicKeyInfoFromOriginatorId(OriginatorId origID) + throws CMSException + { + // TODO Support all alternatives for OriginatorIdentifierOrKey + // see RFC 3852 6.2.2 + throw new CMSException("No support for 'originator' as IssuerAndSerialNumber or SubjectKeyIdentifier"); + } + + protected RecipientOperator getRecipientOperator(Recipient recipient) + throws CMSException, IOException + { + KeyAgreeRecipient agreeRecipient = (KeyAgreeRecipient)recipient; + AlgorithmIdentifier recKeyAlgId = agreeRecipient.getPrivateKeyAlgorithmIdentifier(); + + return ((KeyAgreeRecipient)recipient).getRecipientOperator(keyEncAlg, messageAlgorithm, getSenderPublicKeyInfo(recKeyAlgId, + info.getOriginator()), info.getUserKeyingMaterial(), encryptedKey.getOctets()); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KeyTransRecipient.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KeyTransRecipient.java new file mode 100644 index 000000000..04201d0d1 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KeyTransRecipient.java @@ -0,0 +1,10 @@ +package org.spongycastle.cms; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +public interface KeyTransRecipient + extends Recipient +{ + RecipientOperator getRecipientOperator(AlgorithmIdentifier keyEncAlg, AlgorithmIdentifier contentEncryptionAlgorithm, byte[] encryptedContentKey) + throws CMSException; +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KeyTransRecipientId.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KeyTransRecipientId.java new file mode 100644 index 000000000..b76e19f42 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KeyTransRecipientId.java @@ -0,0 +1,102 @@ +package org.spongycastle.cms; + +import java.math.BigInteger; + +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.cert.selector.X509CertificateHolderSelector; + +public class KeyTransRecipientId + extends RecipientId +{ + private X509CertificateHolderSelector baseSelector; + + private KeyTransRecipientId(X509CertificateHolderSelector baseSelector) + { + super(keyTrans); + + this.baseSelector = baseSelector; + } + + /** + * Construct a key trans recipient ID with the value of a public key's subjectKeyId. + * + * @param subjectKeyId a subjectKeyId + */ + public KeyTransRecipientId(byte[] subjectKeyId) + { + this(null, null, subjectKeyId); + } + + /** + * Construct a key trans recipient ID based on the issuer and serial number of the recipient's associated + * certificate. + * + * @param issuer the issuer of the recipient's associated certificate. + * @param serialNumber the serial number of the recipient's associated certificate. + */ + public KeyTransRecipientId(X500Name issuer, BigInteger serialNumber) + { + this(issuer, serialNumber, null); + } + + /** + * Construct a key trans recipient ID based on the issuer and serial number of the recipient's associated + * certificate. + * + * @param issuer the issuer of the recipient's associated certificate. + * @param serialNumber the serial number of the recipient's associated certificate. + * @param subjectKeyId the subject key identifier to use to match the recipients associated certificate. + */ + public KeyTransRecipientId(X500Name issuer, BigInteger serialNumber, byte[] subjectKeyId) + { + this(new X509CertificateHolderSelector(issuer, serialNumber, subjectKeyId)); + } + + public X500Name getIssuer() + { + return baseSelector.getIssuer(); + } + + public BigInteger getSerialNumber() + { + return baseSelector.getSerialNumber(); + } + + public byte[] getSubjectKeyIdentifier() + { + return baseSelector.getSubjectKeyIdentifier(); + } + + public int hashCode() + { + return baseSelector.hashCode(); + } + + public boolean equals( + Object o) + { + if (!(o instanceof KeyTransRecipientId)) + { + return false; + } + + KeyTransRecipientId id = (KeyTransRecipientId)o; + + return this.baseSelector.equals(id.baseSelector); + } + + public Object clone() + { + return new KeyTransRecipientId(this.baseSelector); + } + + public boolean match(Object obj) + { + if (obj instanceof KeyTransRecipientInformation) + { + return ((KeyTransRecipientInformation)obj).getRID().equals(this); + } + + return baseSelector.match(obj); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KeyTransRecipientInfoGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KeyTransRecipientInfoGenerator.java new file mode 100644 index 000000000..c78a4ff88 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KeyTransRecipientInfoGenerator.java @@ -0,0 +1,58 @@ +package org.spongycastle.cms; + +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.cms.IssuerAndSerialNumber; +import org.spongycastle.asn1.cms.KeyTransRecipientInfo; +import org.spongycastle.asn1.cms.RecipientIdentifier; +import org.spongycastle.asn1.cms.RecipientInfo; +import org.spongycastle.operator.AsymmetricKeyWrapper; +import org.spongycastle.operator.GenericKey; +import org.spongycastle.operator.OperatorException; + +public abstract class KeyTransRecipientInfoGenerator + implements RecipientInfoGenerator +{ + protected final AsymmetricKeyWrapper wrapper; + + private IssuerAndSerialNumber issuerAndSerial; + private byte[] subjectKeyIdentifier; + + protected KeyTransRecipientInfoGenerator(IssuerAndSerialNumber issuerAndSerial, AsymmetricKeyWrapper wrapper) + { + this.issuerAndSerial = issuerAndSerial; + this.wrapper = wrapper; + } + + protected KeyTransRecipientInfoGenerator(byte[] subjectKeyIdentifier, AsymmetricKeyWrapper wrapper) + { + this.subjectKeyIdentifier = subjectKeyIdentifier; + this.wrapper = wrapper; + } + + public final RecipientInfo generate(GenericKey contentEncryptionKey) + throws CMSException + { + byte[] encryptedKeyBytes; + try + { + encryptedKeyBytes = wrapper.generateWrappedKey(contentEncryptionKey); + } + catch (OperatorException e) + { + throw new CMSException("exception wrapping content key: " + e.getMessage(), e); + } + + RecipientIdentifier recipId; + if (issuerAndSerial != null) + { + recipId = new RecipientIdentifier(issuerAndSerial); + } + else + { + recipId = new RecipientIdentifier(new DEROctetString(subjectKeyIdentifier)); + } + + return new RecipientInfo(new KeyTransRecipientInfo(recipId, wrapper.getAlgorithmIdentifier(), + new DEROctetString(encryptedKeyBytes))); + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KeyTransRecipientInformation.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KeyTransRecipientInformation.java new file mode 100644 index 000000000..f671b8cd2 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/KeyTransRecipientInformation.java @@ -0,0 +1,50 @@ +package org.spongycastle.cms; + +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.cms.IssuerAndSerialNumber; +import org.spongycastle.asn1.cms.KeyTransRecipientInfo; +import org.spongycastle.asn1.cms.RecipientIdentifier; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +/** + * the KeyTransRecipientInformation class for a recipient who has been sent a secret + * key encrypted using their public key that needs to be used to + * extract the message. + */ +public class KeyTransRecipientInformation + extends RecipientInformation +{ + private KeyTransRecipientInfo info; + + KeyTransRecipientInformation( + KeyTransRecipientInfo info, + AlgorithmIdentifier messageAlgorithm, + CMSSecureReadable secureReadable, + AuthAttributesProvider additionalData) + { + super(info.getKeyEncryptionAlgorithm(), messageAlgorithm, secureReadable, additionalData); + + this.info = info; + + RecipientIdentifier r = info.getRecipientIdentifier(); + + if (r.isTagged()) + { + ASN1OctetString octs = ASN1OctetString.getInstance(r.getId()); + + rid = new KeyTransRecipientId(octs.getOctets()); + } + else + { + IssuerAndSerialNumber iAnds = IssuerAndSerialNumber.getInstance(r.getId()); + + rid = new KeyTransRecipientId(iAnds.getName(), iAnds.getSerialNumber().getValue()); + } + } + + protected RecipientOperator getRecipientOperator(Recipient recipient) + throws CMSException + { + return ((KeyTransRecipient)recipient).getRecipientOperator(keyEncAlg, messageAlgorithm, info.getEncryptedKey().getOctets()); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/NullOutputStream.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/NullOutputStream.java new file mode 100644 index 000000000..c5ccd4ec5 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/NullOutputStream.java @@ -0,0 +1,28 @@ +/** + * + */ +package org.spongycastle.cms; + +import java.io.IOException; +import java.io.OutputStream; + +class NullOutputStream + extends OutputStream +{ + public void write(byte[] buf) + throws IOException + { + // do nothing + } + + public void write(byte[] buf, int off, int len) + throws IOException + { + // do nothing + } + + public void write(int b) throws IOException + { + // do nothing + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/OriginatorId.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/OriginatorId.java new file mode 100644 index 000000000..eee06cea7 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/OriginatorId.java @@ -0,0 +1,118 @@ +package org.spongycastle.cms; + +import java.math.BigInteger; + +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.util.Arrays; +import org.spongycastle.util.Selector; + +/** + * a basic index for an originator. + */ +class OriginatorId + implements Selector +{ + private byte[] subjectKeyId; + + private X500Name issuer; + private BigInteger serialNumber; + + /** + * Construct a signer ID with the value of a public key's subjectKeyId. + * + * @param subjectKeyId a subjectKeyId + */ + public OriginatorId(byte[] subjectKeyId) + { + setSubjectKeyID(subjectKeyId); + } + + private void setSubjectKeyID(byte[] subjectKeyId) + { + this.subjectKeyId = subjectKeyId; + } + + /** + * Construct a signer ID based on the issuer and serial number of the signer's associated + * certificate. + * + * @param issuer the issuer of the signer's associated certificate. + * @param serialNumber the serial number of the signer's associated certificate. + */ + public OriginatorId(X500Name issuer, BigInteger serialNumber) + { + setIssuerAndSerial(issuer, serialNumber); + } + + private void setIssuerAndSerial(X500Name issuer, BigInteger serialNumber) + { + this.issuer = issuer; + this.serialNumber = serialNumber; + } + + /** + * Construct a signer ID based on the issuer and serial number of the signer's associated + * certificate. + * + * @param issuer the issuer of the signer's associated certificate. + * @param serialNumber the serial number of the signer's associated certificate. + * @param subjectKeyId the subject key identifier to use to match the signers associated certificate. + */ + public OriginatorId(X500Name issuer, BigInteger serialNumber, byte[] subjectKeyId) + { + setIssuerAndSerial(issuer, serialNumber); + setSubjectKeyID(subjectKeyId); + } + + public X500Name getIssuer() + { + return issuer; + } + + public Object clone() + { + return new OriginatorId(this.issuer, this.serialNumber, this.subjectKeyId); + } + + public int hashCode() + { + int code = Arrays.hashCode(subjectKeyId); + + if (this.serialNumber != null) + { + code ^= this.serialNumber.hashCode(); + } + + if (this.issuer != null) + { + code ^= this.issuer.hashCode(); + } + + return code; + } + + public boolean equals( + Object o) + { + if (!(o instanceof OriginatorId)) + { + return false; + } + + OriginatorId id = (OriginatorId)o; + + return Arrays.areEqual(subjectKeyId, id.subjectKeyId) + && equalsObj(this.serialNumber, id.serialNumber) + && equalsObj(this.issuer, id.issuer); + } + + private boolean equalsObj(Object a, Object b) + { + return (a != null) ? a.equals(b) : b == null; + } + + public boolean match(Object obj) + { + return false; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/OriginatorInfoGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/OriginatorInfoGenerator.java new file mode 100644 index 000000000..fd70ab525 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/OriginatorInfoGenerator.java @@ -0,0 +1,54 @@ +package org.spongycastle.cms; + +import java.util.ArrayList; +import java.util.List; + +import org.spongycastle.asn1.cms.OriginatorInfo; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.util.Store; + +public class OriginatorInfoGenerator +{ + private final List origCerts; + private final List origCRLs; + + public OriginatorInfoGenerator(X509CertificateHolder origCert) + { + this.origCerts = new ArrayList(1); + this.origCRLs = null; + origCerts.add(origCert.toASN1Structure()); + } + + public OriginatorInfoGenerator(Store origCerts) + throws CMSException + { + this(origCerts, null); + } + + public OriginatorInfoGenerator(Store origCerts, Store origCRLs) + throws CMSException + { + this.origCerts = CMSUtils.getCertificatesFromStore(origCerts); + + if (origCRLs != null) + { + this.origCRLs = CMSUtils.getCRLsFromStore(origCRLs); + } + else + { + this.origCRLs = null; + } + } + + public OriginatorInformation generate() + { + if (origCRLs != null) + { + return new OriginatorInformation(new OriginatorInfo(CMSUtils.createDerSetFromList(origCerts), CMSUtils.createDerSetFromList(origCRLs))); + } + else + { + return new OriginatorInformation(new OriginatorInfo(CMSUtils.createDerSetFromList(origCerts), null)); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/OriginatorInformation.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/OriginatorInformation.java new file mode 100644 index 000000000..4db9a10a9 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/OriginatorInformation.java @@ -0,0 +1,95 @@ +package org.spongycastle.cms; + +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.ASN1Set; +import org.spongycastle.asn1.cms.OriginatorInfo; +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.util.CollectionStore; +import org.spongycastle.util.Store; + +public class OriginatorInformation +{ + private OriginatorInfo originatorInfo; + + OriginatorInformation(OriginatorInfo originatorInfo) + { + this.originatorInfo = originatorInfo; + } + + /** + * Return the certificates stored in the underlying OriginatorInfo object. + * + * @return a Store of X509CertificateHolder objects. + */ + public Store getCertificates() + { + ASN1Set certSet = originatorInfo.getCertificates(); + + if (certSet != null) + { + List certList = new ArrayList(certSet.size()); + + for (Enumeration en = certSet.getObjects(); en.hasMoreElements();) + { + ASN1Primitive obj = ((ASN1Encodable)en.nextElement()).toASN1Primitive(); + + if (obj instanceof ASN1Sequence) + { + certList.add(new X509CertificateHolder(Certificate.getInstance(obj))); + } + } + + return new CollectionStore(certList); + } + + return new CollectionStore(new ArrayList()); + } + + /** + * Return the CRLs stored in the underlying OriginatorInfo object. + * + * @return a Store of X509CRLHolder objects. + */ + public Store getCRLs() + { + ASN1Set crlSet = originatorInfo.getCRLs(); + + if (crlSet != null) + { + List crlList = new ArrayList(crlSet.size()); + + for (Enumeration en = crlSet.getObjects(); en.hasMoreElements();) + { + ASN1Primitive obj = ((ASN1Encodable)en.nextElement()).toASN1Primitive(); + + if (obj instanceof ASN1Sequence) + { + crlList.add(new X509CRLHolder(CertificateList.getInstance(obj))); + } + } + + return new CollectionStore(crlList); + } + + return new CollectionStore(new ArrayList()); + } + + /** + * Return the underlying ASN.1 object defining this SignerInformation object. + * + * @return a OriginatorInfo. + */ + public OriginatorInfo toASN1Structure() + { + return originatorInfo; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/PasswordRecipient.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/PasswordRecipient.java new file mode 100644 index 000000000..b2762dd01 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/PasswordRecipient.java @@ -0,0 +1,17 @@ +package org.spongycastle.cms; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +public interface PasswordRecipient + extends Recipient +{ + public static final int PKCS5_SCHEME2 = 0; + public static final int PKCS5_SCHEME2_UTF8 = 1; + + RecipientOperator getRecipientOperator(AlgorithmIdentifier keyEncryptionAlgorithm, AlgorithmIdentifier contentEncryptionAlgorithm, byte[] derivedKey, byte[] encryptedEncryptedContentKey) + throws CMSException; + + int getPasswordConversionScheme(); + + char[] getPassword(); +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/PasswordRecipientId.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/PasswordRecipientId.java new file mode 100644 index 000000000..d3efe2f51 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/PasswordRecipientId.java @@ -0,0 +1,44 @@ +package org.spongycastle.cms; + +public class PasswordRecipientId + extends RecipientId +{ + /** + * Construct a recipient ID of the password type. + */ + public PasswordRecipientId() + { + super(password); + } + + public int hashCode() + { + return password; + } + + public boolean equals( + Object o) + { + if (!(o instanceof PasswordRecipientId)) + { + return false; + } + + return true; + } + + public Object clone() + { + return new PasswordRecipientId(); + } + + public boolean match(Object obj) + { + if (obj instanceof PasswordRecipientInformation) + { + return true; + } + + return false; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/PasswordRecipientInfoGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/PasswordRecipientInfoGenerator.java new file mode 100644 index 000000000..aa1b5a0b2 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/PasswordRecipientInfoGenerator.java @@ -0,0 +1,138 @@ +package org.spongycastle.cms; + +import java.security.SecureRandom; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.asn1.cms.PasswordRecipientInfo; +import org.spongycastle.asn1.cms.RecipientInfo; +import org.spongycastle.asn1.pkcs.PBKDF2Params; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.crypto.PBEParametersGenerator; +import org.spongycastle.crypto.generators.PKCS5S2ParametersGenerator; +import org.spongycastle.crypto.params.KeyParameter; +import org.spongycastle.operator.GenericKey; + +public abstract class PasswordRecipientInfoGenerator + implements RecipientInfoGenerator +{ + private char[] password; + private AlgorithmIdentifier keyDerivationAlgorithm; + private ASN1ObjectIdentifier kekAlgorithm; + private SecureRandom random; + private int schemeID; + private int keySize; + private int blockSize; + + protected PasswordRecipientInfoGenerator(ASN1ObjectIdentifier kekAlgorithm, char[] password) + { + this(kekAlgorithm, password, getKeySize(kekAlgorithm), ((Integer)PasswordRecipientInformation.BLOCKSIZES.get(kekAlgorithm)).intValue()); + } + + protected PasswordRecipientInfoGenerator(ASN1ObjectIdentifier kekAlgorithm, char[] password, int keySize, int blockSize) + { + this.password = password; + this.schemeID = PasswordRecipient.PKCS5_SCHEME2_UTF8; + this.kekAlgorithm = kekAlgorithm; + this.keySize = keySize; + this.blockSize = blockSize; + } + + private static int getKeySize(ASN1ObjectIdentifier kekAlgorithm) + { + Integer size = (Integer)PasswordRecipientInformation.KEYSIZES.get(kekAlgorithm); + + if (size == null) + { + throw new IllegalArgumentException("cannot find key size for algorithm: " + kekAlgorithm); + } + + return size.intValue(); + } + + public PasswordRecipientInfoGenerator setPasswordConversionScheme(int schemeID) + { + this.schemeID = schemeID; + + return this; + } + + public PasswordRecipientInfoGenerator setSaltAndIterationCount(byte[] salt, int iterationCount) + { + this.keyDerivationAlgorithm = new AlgorithmIdentifier(PKCSObjectIdentifiers.id_PBKDF2, new PBKDF2Params(salt, iterationCount)); + + return this; + } + + public PasswordRecipientInfoGenerator setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + public RecipientInfo generate(GenericKey contentEncryptionKey) + throws CMSException + { + byte[] iv = new byte[blockSize]; /// TODO: set IV size properly! + + if (random == null) + { + random = new SecureRandom(); + } + + random.nextBytes(iv); + + if (keyDerivationAlgorithm == null) + { + byte[] salt = new byte[20]; + + random.nextBytes(salt); + + keyDerivationAlgorithm = new AlgorithmIdentifier(PKCSObjectIdentifiers.id_PBKDF2, new PBKDF2Params(salt, 1024)); + } + + PBKDF2Params params = PBKDF2Params.getInstance(keyDerivationAlgorithm.getParameters()); + byte[] derivedKey; + + if (schemeID == PasswordRecipient.PKCS5_SCHEME2) + { + PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(); + + gen.init(PBEParametersGenerator.PKCS5PasswordToBytes(password), params.getSalt(), params.getIterationCount().intValue()); + + derivedKey = ((KeyParameter)gen.generateDerivedParameters(keySize)).getKey(); + } + else + { + PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(); + + gen.init(PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(password), params.getSalt(), params.getIterationCount().intValue()); + + derivedKey = ((KeyParameter)gen.generateDerivedParameters(keySize)).getKey(); + } + + AlgorithmIdentifier kekAlgorithmId = new AlgorithmIdentifier(kekAlgorithm, new DEROctetString(iv)); + + byte[] encryptedKeyBytes = generateEncryptedBytes(kekAlgorithmId, derivedKey, contentEncryptionKey); + + ASN1OctetString encryptedKey = new DEROctetString(encryptedKeyBytes); + + ASN1EncodableVector v = new ASN1EncodableVector(); + v.add(kekAlgorithm); + v.add(new DEROctetString(iv)); + + AlgorithmIdentifier keyEncryptionAlgorithm = new AlgorithmIdentifier( + PKCSObjectIdentifiers.id_alg_PWRI_KEK, new DERSequence(v)); + + return new RecipientInfo(new PasswordRecipientInfo(keyDerivationAlgorithm, + keyEncryptionAlgorithm, encryptedKey)); + } + + protected abstract byte[] generateEncryptedBytes(AlgorithmIdentifier algorithm, byte[] derivedKey, GenericKey contentEncryptionKey) + throws CMSException; +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/PasswordRecipientInformation.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/PasswordRecipientInformation.java new file mode 100644 index 000000000..90b7c4996 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/PasswordRecipientInformation.java @@ -0,0 +1,135 @@ +package org.spongycastle.cms; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.cms.PasswordRecipientInfo; +import org.spongycastle.asn1.pkcs.PBKDF2Params; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.crypto.PBEParametersGenerator; +import org.spongycastle.crypto.generators.PKCS5S2ParametersGenerator; +import org.spongycastle.crypto.params.KeyParameter; +import org.spongycastle.util.Integers; + +/** + * the RecipientInfo class for a recipient who has been sent a message + * encrypted using a password. + */ +public class PasswordRecipientInformation + extends RecipientInformation +{ + static Map KEYSIZES = new HashMap(); + static Map BLOCKSIZES = new HashMap(); + + static + { + BLOCKSIZES.put(CMSAlgorithm.DES_EDE3_CBC, Integers.valueOf(8)); + BLOCKSIZES.put(CMSAlgorithm.AES128_CBC, Integers.valueOf(16)); + BLOCKSIZES.put(CMSAlgorithm.AES192_CBC, Integers.valueOf(16)); + BLOCKSIZES.put(CMSAlgorithm.AES256_CBC, Integers.valueOf(16)); + + KEYSIZES.put(CMSAlgorithm.DES_EDE3_CBC, Integers.valueOf(192)); + KEYSIZES.put(CMSAlgorithm.AES128_CBC, Integers.valueOf(128)); + KEYSIZES.put(CMSAlgorithm.AES192_CBC, Integers.valueOf(192)); + KEYSIZES.put(CMSAlgorithm.AES256_CBC, Integers.valueOf(256)); + } + + private PasswordRecipientInfo info; + + PasswordRecipientInformation( + PasswordRecipientInfo info, + AlgorithmIdentifier messageAlgorithm, + CMSSecureReadable secureReadable, + AuthAttributesProvider additionalData) + { + super(info.getKeyEncryptionAlgorithm(), messageAlgorithm, secureReadable, additionalData); + + this.info = info; + this.rid = new PasswordRecipientId(); + } + + /** + * return the object identifier for the key derivation algorithm, or null + * if there is none present. + * + * @return OID for key derivation algorithm, if present. + */ + public String getKeyDerivationAlgOID() + { + if (info.getKeyDerivationAlgorithm() != null) + { + return info.getKeyDerivationAlgorithm().getAlgorithm().getId(); + } + + return null; + } + + /** + * return the ASN.1 encoded key derivation algorithm parameters, or null if + * there aren't any. + * @return ASN.1 encoding of key derivation algorithm parameters. + */ + public byte[] getKeyDerivationAlgParams() + { + try + { + if (info.getKeyDerivationAlgorithm() != null) + { + ASN1Encodable params = info.getKeyDerivationAlgorithm().getParameters(); + if (params != null) + { + return params.toASN1Primitive().getEncoded(); + } + } + + return null; + } + catch (Exception e) + { + throw new RuntimeException("exception getting encryption parameters " + e); + } + } + + /** + * Return the key derivation algorithm details for the key in this recipient. + * + * @return AlgorithmIdentifier representing the key derivation algorithm. + */ + public AlgorithmIdentifier getKeyDerivationAlgorithm() + { + return info.getKeyDerivationAlgorithm(); + } + + protected RecipientOperator getRecipientOperator(Recipient recipient) + throws CMSException, IOException + { + PasswordRecipient pbeRecipient = (PasswordRecipient)recipient; + AlgorithmIdentifier kekAlg = AlgorithmIdentifier.getInstance(info.getKeyEncryptionAlgorithm()); + AlgorithmIdentifier kekAlgParams = AlgorithmIdentifier.getInstance(kekAlg.getParameters()); + + byte[] passwordBytes = getPasswordBytes(pbeRecipient.getPasswordConversionScheme(), + pbeRecipient.getPassword()); + PBKDF2Params params = PBKDF2Params.getInstance(info.getKeyDerivationAlgorithm().getParameters()); + + PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(); + gen.init(passwordBytes, params.getSalt(), params.getIterationCount().intValue()); + + int keySize = ((Integer)KEYSIZES.get(kekAlgParams.getAlgorithm())).intValue(); + + byte[] derivedKey = ((KeyParameter)gen.generateDerivedParameters(keySize)).getKey(); + + return pbeRecipient.getRecipientOperator(kekAlgParams, messageAlgorithm, derivedKey, info.getEncryptedKey().getOctets()); + } + + protected byte[] getPasswordBytes(int scheme, char[] password) + { + if (scheme == PasswordRecipient.PKCS5_SCHEME2) + { + return PBEParametersGenerator.PKCS5PasswordToBytes(password); + } + + return PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(password); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/Recipient.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/Recipient.java new file mode 100644 index 000000000..17a3e4f8f --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/Recipient.java @@ -0,0 +1,5 @@ +package org.spongycastle.cms; + +public interface Recipient +{ +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/RecipientId.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/RecipientId.java new file mode 100644 index 000000000..78a4bc22d --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/RecipientId.java @@ -0,0 +1,31 @@ +package org.spongycastle.cms; + +import org.spongycastle.util.Selector; + +public abstract class RecipientId + implements Selector +{ + public static final int keyTrans = 0; + public static final int kek = 1; + public static final int keyAgree = 2; + public static final int password = 3; + + private final int type; + + protected RecipientId(int type) + { + this.type = type; + } + + /** + * Return the type code for this recipient ID. + * + * @return one of keyTrans, kek, keyAgree, password + */ + public int getType() + { + return type; + } + + public abstract Object clone(); +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/RecipientInfoGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/RecipientInfoGenerator.java new file mode 100644 index 000000000..f61b3d971 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/RecipientInfoGenerator.java @@ -0,0 +1,10 @@ +package org.spongycastle.cms; + +import org.spongycastle.asn1.cms.RecipientInfo; +import org.spongycastle.operator.GenericKey; + +public interface RecipientInfoGenerator +{ + RecipientInfo generate(GenericKey contentEncryptionKey) + throws CMSException; +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/RecipientInformation.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/RecipientInformation.java new file mode 100644 index 000000000..c083baa92 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/RecipientInformation.java @@ -0,0 +1,181 @@ +package org.spongycastle.cms; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1Encoding; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.util.io.Streams; + +public abstract class RecipientInformation +{ + protected RecipientId rid; + protected AlgorithmIdentifier keyEncAlg; + protected AlgorithmIdentifier messageAlgorithm; + protected CMSSecureReadable secureReadable; + + private AuthAttributesProvider additionalData; + + private byte[] resultMac; + private RecipientOperator operator; + + RecipientInformation( + AlgorithmIdentifier keyEncAlg, + AlgorithmIdentifier messageAlgorithm, + CMSSecureReadable secureReadable, + AuthAttributesProvider additionalData) + { + this.keyEncAlg = keyEncAlg; + this.messageAlgorithm = messageAlgorithm; + this.secureReadable = secureReadable; + this.additionalData = additionalData; + } + + public RecipientId getRID() + { + return rid; + } + + private byte[] encodeObj( + ASN1Encodable obj) + throws IOException + { + if (obj != null) + { + return obj.toASN1Primitive().getEncoded(); + } + + return null; + } + + /** + * Return the key encryption algorithm details for the key in this recipient. + * + * @return AlgorithmIdentifier representing the key encryption algorithm. + */ + public AlgorithmIdentifier getKeyEncryptionAlgorithm() + { + return keyEncAlg; + } + + /** + * return the object identifier for the key encryption algorithm. + * + * @return OID for key encryption algorithm. + */ + public String getKeyEncryptionAlgOID() + { + return keyEncAlg.getAlgorithm().getId(); + } + + /** + * return the ASN.1 encoded key encryption algorithm parameters, or null if + * there aren't any. + * + * @return ASN.1 encoding of key encryption algorithm parameters. + */ + public byte[] getKeyEncryptionAlgParams() + { + try + { + return encodeObj(keyEncAlg.getParameters()); + } + catch (Exception e) + { + throw new RuntimeException("exception getting encryption parameters " + e); + } + } + + /** + * Return the content digest calculated during the read of the content if one has been generated. This will + * only happen if we are dealing with authenticated data and authenticated attributes are present. + * + * @return byte array containing the digest. + */ + public byte[] getContentDigest() + { + if (secureReadable instanceof CMSEnvelopedHelper.CMSDigestAuthenticatedSecureReadable) + { + return ((CMSEnvelopedHelper.CMSDigestAuthenticatedSecureReadable)secureReadable).getDigest(); + } + + return null; + } + + /** + * Return the MAC calculated for the recipient. Note: this call is only meaningful once all + * the content has been read. + * + * @return byte array containing the mac. + */ + public byte[] getMac() + { + if (resultMac == null) + { + if (operator.isMacBased()) + { + if (additionalData != null) + { + try + { + Streams.drain(operator.getInputStream(new ByteArrayInputStream(additionalData.getAuthAttributes().getEncoded(ASN1Encoding.DER)))); + } + catch (IOException e) + { + throw new IllegalStateException("unable to drain input: " + e.getMessage()); + } + } + resultMac = operator.getMac(); + } + } + + return resultMac; + } + + /** + * Return the decrypted/encapsulated content in the EnvelopedData after recovering the content + * encryption/MAC key using the passed in Recipient. + * + * @param recipient recipient object to use to recover content encryption key + * @return the content inside the EnvelopedData this RecipientInformation is associated with. + * @throws CMSException if the content-encryption/MAC key cannot be recovered. + */ + public byte[] getContent( + Recipient recipient) + throws CMSException + { + try + { + return CMSUtils.streamToByteArray(getContentStream(recipient).getContentStream()); + } + catch (IOException e) + { + throw new CMSException("unable to parse internal stream: " + e.getMessage(), e); + } + } + + /** + * Return a CMSTypedStream representing the content in the EnvelopedData after recovering the content + * encryption/MAC key using the passed in Recipient. + * + * @param recipient recipient object to use to recover content encryption key + * @return the content inside the EnvelopedData this RecipientInformation is associated with. + * @throws CMSException if the content-encryption/MAC key cannot be recovered. + */ + public CMSTypedStream getContentStream(Recipient recipient) + throws CMSException, IOException + { + operator = getRecipientOperator(recipient); + + if (additionalData != null) + { + return new CMSTypedStream(secureReadable.getInputStream()); + } + + return new CMSTypedStream(operator.getInputStream(secureReadable.getInputStream())); + } + + protected abstract RecipientOperator getRecipientOperator(Recipient recipient) + throws CMSException, IOException; +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/RecipientInformationStore.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/RecipientInformationStore.java new file mode 100644 index 000000000..f995874c2 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/RecipientInformationStore.java @@ -0,0 +1,115 @@ +package org.spongycastle.cms; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.spongycastle.asn1.x500.X500Name; + +public class RecipientInformationStore +{ + private final List all; //ArrayList[RecipientInformation] + private final Map table = new HashMap(); // HashMap[RecipientID, ArrayList[RecipientInformation]] + + public RecipientInformationStore( + Collection recipientInfos) + { + Iterator it = recipientInfos.iterator(); + + while (it.hasNext()) + { + RecipientInformation recipientInformation = (RecipientInformation)it.next(); + RecipientId rid = recipientInformation.getRID(); + + List list = (ArrayList)table.get(rid); + if (list == null) + { + list = new ArrayList(1); + table.put(rid, list); + } + + list.add(recipientInformation); + } + + this.all = new ArrayList(recipientInfos); + } + + /** + * Return the first RecipientInformation object that matches the + * passed in selector. Null if there are no matches. + * + * @param selector to identify a recipient + * @return a single RecipientInformation object. Null if none matches. + */ + public RecipientInformation get( + RecipientId selector) + { + Collection list = getRecipients(selector); + + return list.size() == 0 ? null : (RecipientInformation)list.iterator().next(); + } + + /** + * Return the number of recipients in the collection. + * + * @return number of recipients identified. + */ + public int size() + { + return all.size(); + } + + /** + * Return all recipients in the collection + * + * @return a collection of recipients. + */ + public Collection getRecipients() + { + return new ArrayList(all); + } + + /** + * Return possible empty collection with recipients matching the passed in RecipientId + * + * @param selector a recipient id to select against. + * @return a collection of RecipientInformation objects. + */ + public Collection getRecipients( + RecipientId selector) + { + if (selector instanceof KeyTransRecipientId) + { + KeyTransRecipientId keyTrans = (KeyTransRecipientId)selector; + + X500Name issuer = keyTrans.getIssuer(); + byte[] subjectKeyId = keyTrans.getSubjectKeyIdentifier(); + + if (issuer != null && subjectKeyId != null) + { + List results = new ArrayList(); + + Collection match1 = getRecipients(new KeyTransRecipientId(issuer, keyTrans.getSerialNumber())); + if (match1 != null) + { + results.addAll(match1); + } + + Collection match2 = getRecipients(new KeyTransRecipientId(subjectKeyId)); + if (match2 != null) + { + results.addAll(match2); + } + + return results; + } + } + + List list = (ArrayList)table.get(selector); + + return list == null ? new ArrayList() : new ArrayList(list); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/RecipientOperator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/RecipientOperator.java new file mode 100644 index 000000000..c254dbf35 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/RecipientOperator.java @@ -0,0 +1,48 @@ +package org.spongycastle.cms; + +import java.io.InputStream; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.operator.InputDecryptor; +import org.spongycastle.operator.MacCalculator; +import org.spongycastle.util.io.TeeInputStream; + +public class RecipientOperator +{ + private final AlgorithmIdentifier algorithmIdentifier; + private final Object operator; + + public RecipientOperator(InputDecryptor decryptor) + { + this.algorithmIdentifier = decryptor.getAlgorithmIdentifier(); + this.operator = decryptor; + } + + public RecipientOperator(MacCalculator macCalculator) + { + this.algorithmIdentifier = macCalculator.getAlgorithmIdentifier(); + this.operator = macCalculator; + } + + public InputStream getInputStream(InputStream dataIn) + { + if (operator instanceof InputDecryptor) + { + return ((InputDecryptor)operator).getInputStream(dataIn); + } + else + { + return new TeeInputStream(dataIn, ((MacCalculator)operator).getOutputStream()); + } + } + + public boolean isMacBased() + { + return operator instanceof MacCalculator; + } + + public byte[] getMac() + { + return ((MacCalculator)operator).getMac(); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/SignerId.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/SignerId.java new file mode 100644 index 000000000..a7434f8a6 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/SignerId.java @@ -0,0 +1,104 @@ +package org.spongycastle.cms; + +import java.math.BigInteger; + +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.cert.selector.X509CertificateHolderSelector; +import org.spongycastle.util.Selector; + +/** + * a basic index for a signer. + */ +public class SignerId + implements Selector +{ + private X509CertificateHolderSelector baseSelector; + + private SignerId(X509CertificateHolderSelector baseSelector) + { + this.baseSelector = baseSelector; + } + + /** + * Construct a signer ID with the value of a public key's subjectKeyId. + * + * @param subjectKeyId a subjectKeyId + */ + public SignerId(byte[] subjectKeyId) + { + this(null, null, subjectKeyId); + } + + /** + * Construct a signer ID based on the issuer and serial number of the signer's associated + * certificate. + * + * @param issuer the issuer of the signer's associated certificate. + * @param serialNumber the serial number of the signer's associated certificate. + */ + public SignerId(X500Name issuer, BigInteger serialNumber) + { + this(issuer, serialNumber, null); + } + + /** + * Construct a signer ID based on the issuer and serial number of the signer's associated + * certificate. + * + * @param issuer the issuer of the signer's associated certificate. + * @param serialNumber the serial number of the signer's associated certificate. + * @param subjectKeyId the subject key identifier to use to match the signers associated certificate. + */ + public SignerId(X500Name issuer, BigInteger serialNumber, byte[] subjectKeyId) + { + this(new X509CertificateHolderSelector(issuer, serialNumber, subjectKeyId)); + } + + public X500Name getIssuer() + { + return baseSelector.getIssuer(); + } + + public BigInteger getSerialNumber() + { + return baseSelector.getSerialNumber(); + } + + public byte[] getSubjectKeyIdentifier() + { + return baseSelector.getSubjectKeyIdentifier(); + } + + public int hashCode() + { + return baseSelector.hashCode(); + } + + public boolean equals( + Object o) + { + if (!(o instanceof SignerId)) + { + return false; + } + + SignerId id = (SignerId)o; + + return this.baseSelector.equals(id.baseSelector); + } + + public boolean match(Object obj) + { + if (obj instanceof SignerInformation) + { + return ((SignerInformation)obj).getSID().equals(this); + } + + return baseSelector.match(obj); + } + + public Object clone() + { + return new SignerId(this.baseSelector); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/SignerInfoGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/SignerInfoGenerator.java new file mode 100644 index 000000000..6e5a80886 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/SignerInfoGenerator.java @@ -0,0 +1,291 @@ +package org.spongycastle.cms; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.spongycastle.asn1.ASN1Encoding; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1Set; +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.DERSet; +import org.spongycastle.asn1.cms.AttributeTable; +import org.spongycastle.asn1.cms.SignerIdentifier; +import org.spongycastle.asn1.cms.SignerInfo; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.operator.ContentSigner; +import org.spongycastle.operator.DefaultDigestAlgorithmIdentifierFinder; +import org.spongycastle.operator.DigestAlgorithmIdentifierFinder; +import org.spongycastle.operator.DigestCalculator; +import org.spongycastle.operator.DigestCalculatorProvider; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.util.Arrays; +import org.spongycastle.util.io.TeeOutputStream; + +public class SignerInfoGenerator +{ + private final SignerIdentifier signerIdentifier; + private final CMSAttributeTableGenerator sAttrGen; + private final CMSAttributeTableGenerator unsAttrGen; + private final ContentSigner signer; + private final DigestCalculator digester; + private final DigestAlgorithmIdentifierFinder digAlgFinder = new DefaultDigestAlgorithmIdentifierFinder(); + private final CMSSignatureEncryptionAlgorithmFinder sigEncAlgFinder; + + private byte[] calculatedDigest = null; + private X509CertificateHolder certHolder; + + SignerInfoGenerator( + SignerIdentifier signerIdentifier, + ContentSigner signer, + DigestCalculatorProvider digesterProvider, + CMSSignatureEncryptionAlgorithmFinder sigEncAlgFinder) + throws OperatorCreationException + { + this(signerIdentifier, signer, digesterProvider, sigEncAlgFinder, false); + } + + SignerInfoGenerator( + SignerIdentifier signerIdentifier, + ContentSigner signer, + DigestCalculatorProvider digesterProvider, + CMSSignatureEncryptionAlgorithmFinder sigEncAlgFinder, + boolean isDirectSignature) + throws OperatorCreationException + { + this.signerIdentifier = signerIdentifier; + this.signer = signer; + + if (digesterProvider != null) + { + this.digester = digesterProvider.get(digAlgFinder.find(signer.getAlgorithmIdentifier())); + } + else + { + this.digester = null; + } + + if (isDirectSignature) + { + this.sAttrGen = null; + this.unsAttrGen = null; + } + else + { + this.sAttrGen = new DefaultSignedAttributeTableGenerator(); + this.unsAttrGen = null; + } + + this.sigEncAlgFinder = sigEncAlgFinder; + } + + public SignerInfoGenerator( + SignerInfoGenerator original, + CMSAttributeTableGenerator sAttrGen, + CMSAttributeTableGenerator unsAttrGen) + { + this.signerIdentifier = original.signerIdentifier; + this.signer = original.signer; + this.digester = original.digester; + this.sigEncAlgFinder = original.sigEncAlgFinder; + this.sAttrGen = sAttrGen; + this.unsAttrGen = unsAttrGen; + } + + SignerInfoGenerator( + SignerIdentifier signerIdentifier, + ContentSigner signer, + DigestCalculatorProvider digesterProvider, + CMSSignatureEncryptionAlgorithmFinder sigEncAlgFinder, + CMSAttributeTableGenerator sAttrGen, + CMSAttributeTableGenerator unsAttrGen) + throws OperatorCreationException + { + this.signerIdentifier = signerIdentifier; + this.signer = signer; + + if (digesterProvider != null) + { + this.digester = digesterProvider.get(digAlgFinder.find(signer.getAlgorithmIdentifier())); + } + else + { + this.digester = null; + } + + this.sAttrGen = sAttrGen; + this.unsAttrGen = unsAttrGen; + this.sigEncAlgFinder = sigEncAlgFinder; + } + + public SignerIdentifier getSID() + { + return signerIdentifier; + } + + public int getGeneratedVersion() + { + return signerIdentifier.isTagged() ? 3 : 1; + } + + public boolean hasAssociatedCertificate() + { + return certHolder != null; + } + + public X509CertificateHolder getAssociatedCertificate() + { + return certHolder; + } + + public AlgorithmIdentifier getDigestAlgorithm() + { + if (digester != null) + { + return digester.getAlgorithmIdentifier(); + } + + return digAlgFinder.find(signer.getAlgorithmIdentifier()); + } + + public OutputStream getCalculatingOutputStream() + { + if (digester != null) + { + if (sAttrGen == null) + { + return new TeeOutputStream(digester.getOutputStream(), signer.getOutputStream()); + } + return digester.getOutputStream(); + } + else + { + return signer.getOutputStream(); + } + } + + public SignerInfo generate(ASN1ObjectIdentifier contentType) + throws CMSException + { + try + { + /* RFC 3852 5.4 + * The result of the message digest calculation process depends on + * whether the signedAttrs field is present. When the field is absent, + * the result is just the message digest of the content as described + * + * above. When the field is present, however, the result is the message + * digest of the complete DER encoding of the SignedAttrs value + * contained in the signedAttrs field. + */ + ASN1Set signedAttr = null; + + AlgorithmIdentifier digestAlg = null; + + if (sAttrGen != null) + { + digestAlg = digester.getAlgorithmIdentifier(); + calculatedDigest = digester.getDigest(); + Map parameters = getBaseParameters(contentType, digester.getAlgorithmIdentifier(), calculatedDigest); + AttributeTable signed = sAttrGen.getAttributes(Collections.unmodifiableMap(parameters)); + + signedAttr = getAttributeSet(signed); + + // sig must be composed from the DER encoding. + OutputStream sOut = signer.getOutputStream(); + + sOut.write(signedAttr.getEncoded(ASN1Encoding.DER)); + + sOut.close(); + } + else + { + if (digester != null) + { + digestAlg = digester.getAlgorithmIdentifier(); + calculatedDigest = digester.getDigest(); + } + else + { + digestAlg = digAlgFinder.find(signer.getAlgorithmIdentifier()); + calculatedDigest = null; + } + } + + byte[] sigBytes = signer.getSignature(); + + ASN1Set unsignedAttr = null; + if (unsAttrGen != null) + { + Map parameters = getBaseParameters(contentType, digestAlg, calculatedDigest); + parameters.put(CMSAttributeTableGenerator.SIGNATURE, Arrays.clone(sigBytes)); + + AttributeTable unsigned = unsAttrGen.getAttributes(Collections.unmodifiableMap(parameters)); + + unsignedAttr = getAttributeSet(unsigned); + } + + AlgorithmIdentifier digestEncryptionAlgorithm = sigEncAlgFinder.findEncryptionAlgorithm(signer.getAlgorithmIdentifier()); + + return new SignerInfo(signerIdentifier, digestAlg, + signedAttr, digestEncryptionAlgorithm, new DEROctetString(sigBytes), unsignedAttr); + } + catch (IOException e) + { + throw new CMSException("encoding error.", e); + } + } + + void setAssociatedCertificate(X509CertificateHolder certHolder) + { + this.certHolder = certHolder; + } + + private ASN1Set getAttributeSet( + AttributeTable attr) + { + if (attr != null) + { + return new DERSet(attr.toASN1EncodableVector()); + } + + return null; + } + + private Map getBaseParameters(ASN1ObjectIdentifier contentType, AlgorithmIdentifier digAlgId, byte[] hash) + { + Map param = new HashMap(); + + if (contentType != null) + { + param.put(CMSAttributeTableGenerator.CONTENT_TYPE, contentType); + } + + param.put(CMSAttributeTableGenerator.DIGEST_ALGORITHM_IDENTIFIER, digAlgId); + param.put(CMSAttributeTableGenerator.DIGEST, Arrays.clone(hash)); + return param; + } + + public byte[] getCalculatedDigest() + { + if (calculatedDigest != null) + { + return Arrays.clone(calculatedDigest); + } + + return null; + } + + public CMSAttributeTableGenerator getSignedAttributeTableGenerator() + { + return sAttrGen; + } + + public CMSAttributeTableGenerator getUnsignedAttributeTableGenerator() + { + return unsAttrGen; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/SignerInfoGeneratorBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/SignerInfoGeneratorBuilder.java new file mode 100644 index 000000000..a33005e0c --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/SignerInfoGeneratorBuilder.java @@ -0,0 +1,139 @@ +package org.spongycastle.cms; + +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.cms.IssuerAndSerialNumber; +import org.spongycastle.asn1.cms.SignerIdentifier; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.operator.ContentSigner; +import org.spongycastle.operator.DigestCalculatorProvider; +import org.spongycastle.operator.OperatorCreationException; + +/** + * Builder for SignerInfo generator objects. + */ +public class SignerInfoGeneratorBuilder +{ + private DigestCalculatorProvider digestProvider; + private boolean directSignature; + private CMSAttributeTableGenerator signedGen; + private CMSAttributeTableGenerator unsignedGen; + private CMSSignatureEncryptionAlgorithmFinder sigEncAlgFinder; + + /** + * Base constructor. + * + * @param digestProvider a provider of digest calculators for the algorithms required in the signature and attribute calculations. + */ + public SignerInfoGeneratorBuilder(DigestCalculatorProvider digestProvider) + { + this(digestProvider, new DefaultCMSSignatureEncryptionAlgorithmFinder()); + } + + /** + * Base constructor. + * + * @param digestProvider a provider of digest calculators for the algorithms required in the signature and attribute calculations. + */ + public SignerInfoGeneratorBuilder(DigestCalculatorProvider digestProvider, CMSSignatureEncryptionAlgorithmFinder sigEncAlgFinder) + { + this.digestProvider = digestProvider; + this.sigEncAlgFinder = sigEncAlgFinder; + } + + /** + * If the passed in flag is true, the signer signature will be based on the data, not + * a collection of signed attributes, and no signed attributes will be included. + * + * @return the builder object + */ + public SignerInfoGeneratorBuilder setDirectSignature(boolean hasNoSignedAttributes) + { + this.directSignature = hasNoSignedAttributes; + + return this; + } + + /** + * Provide a custom signed attribute generator. + * + * @param signedGen a generator of signed attributes. + * @return the builder object + */ + public SignerInfoGeneratorBuilder setSignedAttributeGenerator(CMSAttributeTableGenerator signedGen) + { + this.signedGen = signedGen; + + return this; + } + + /** + * Provide a generator of unsigned attributes. + * + * @param unsignedGen a generator for signed attributes. + * @return the builder object + */ + public SignerInfoGeneratorBuilder setUnsignedAttributeGenerator(CMSAttributeTableGenerator unsignedGen) + { + this.unsignedGen = unsignedGen; + + return this; + } + + /** + * Build a generator with the passed in certHolder issuer and serial number as the signerIdentifier. + * + * @param contentSigner operator for generating the final signature in the SignerInfo with. + * @param certHolder carrier for the X.509 certificate related to the contentSigner. + * @return a SignerInfoGenerator + * @throws OperatorCreationException if the generator cannot be built. + */ + public SignerInfoGenerator build(ContentSigner contentSigner, X509CertificateHolder certHolder) + throws OperatorCreationException + { + SignerIdentifier sigId = new SignerIdentifier(new IssuerAndSerialNumber(certHolder.toASN1Structure())); + + SignerInfoGenerator sigInfoGen = createGenerator(contentSigner, sigId); + + sigInfoGen.setAssociatedCertificate(certHolder); + + return sigInfoGen; + } + + /** + * Build a generator with the passed in subjectKeyIdentifier as the signerIdentifier. If used you should + * try to follow the calculation described in RFC 5280 section 4.2.1.2. + * + * @param contentSigner operator for generating the final signature in the SignerInfo with. + * @param subjectKeyIdentifier key identifier to identify the public key for verifying the signature. + * @return a SignerInfoGenerator + * @throws OperatorCreationException if the generator cannot be built. + */ + public SignerInfoGenerator build(ContentSigner contentSigner, byte[] subjectKeyIdentifier) + throws OperatorCreationException + { + SignerIdentifier sigId = new SignerIdentifier(new DEROctetString(subjectKeyIdentifier)); + + return createGenerator(contentSigner, sigId); + } + + private SignerInfoGenerator createGenerator(ContentSigner contentSigner, SignerIdentifier sigId) + throws OperatorCreationException + { + if (directSignature) + { + return new SignerInfoGenerator(sigId, contentSigner, digestProvider, sigEncAlgFinder, true); + } + + if (signedGen != null || unsignedGen != null) + { + if (signedGen == null) + { + signedGen = new DefaultSignedAttributeTableGenerator(); + } + + return new SignerInfoGenerator(sigId, contentSigner, digestProvider, sigEncAlgFinder, signedGen, unsignedGen); + } + + return new SignerInfoGenerator(sigId, contentSigner, digestProvider, sigEncAlgFinder); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/SignerInformation.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/SignerInformation.java new file mode 100644 index 000000000..353f27e6d --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/SignerInformation.java @@ -0,0 +1,680 @@ +package org.spongycastle.cms; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Enumeration; +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.ASN1OctetString; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.ASN1Set; +import org.spongycastle.asn1.DERNull; +import org.spongycastle.asn1.DERSet; +import org.spongycastle.asn1.cms.Attribute; +import org.spongycastle.asn1.cms.AttributeTable; +import org.spongycastle.asn1.cms.CMSAttributes; +import org.spongycastle.asn1.cms.IssuerAndSerialNumber; +import org.spongycastle.asn1.cms.SignerIdentifier; +import org.spongycastle.asn1.cms.SignerInfo; +import org.spongycastle.asn1.cms.Time; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.DigestInfo; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.operator.ContentVerifier; +import org.spongycastle.operator.DigestCalculator; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.operator.RawContentVerifier; +import org.spongycastle.util.Arrays; +import org.spongycastle.util.io.TeeOutputStream; + +/** + * an expanded SignerInfo block from a CMS Signed message + */ +public class SignerInformation +{ + private SignerId sid; + private SignerInfo info; + private AlgorithmIdentifier digestAlgorithm; + private AlgorithmIdentifier encryptionAlgorithm; + private final ASN1Set signedAttributeSet; + private final ASN1Set unsignedAttributeSet; + private CMSProcessable content; + private byte[] signature; + private ASN1ObjectIdentifier contentType; + private byte[] resultDigest; + + // Derived + private AttributeTable signedAttributeValues; + private AttributeTable unsignedAttributeValues; + private boolean isCounterSignature; + + SignerInformation( + SignerInfo info, + ASN1ObjectIdentifier contentType, + CMSProcessable content, + byte[] resultDigest) + { + this.info = info; + this.contentType = contentType; + this.isCounterSignature = contentType == null; + + SignerIdentifier s = info.getSID(); + + if (s.isTagged()) + { + ASN1OctetString octs = ASN1OctetString.getInstance(s.getId()); + + sid = new SignerId(octs.getOctets()); + } + else + { + IssuerAndSerialNumber iAnds = IssuerAndSerialNumber.getInstance(s.getId()); + + sid = new SignerId(iAnds.getName(), iAnds.getSerialNumber().getValue()); + } + + this.digestAlgorithm = info.getDigestAlgorithm(); + this.signedAttributeSet = info.getAuthenticatedAttributes(); + this.unsignedAttributeSet = info.getUnauthenticatedAttributes(); + this.encryptionAlgorithm = info.getDigestEncryptionAlgorithm(); + this.signature = info.getEncryptedDigest().getOctets(); + + this.content = content; + this.resultDigest = resultDigest; + } + + public boolean isCounterSignature() + { + return isCounterSignature; + } + + public ASN1ObjectIdentifier getContentType() + { + return this.contentType; + } + + private byte[] encodeObj( + ASN1Encodable obj) + throws IOException + { + if (obj != null) + { + return obj.toASN1Primitive().getEncoded(); + } + + return null; + } + + public SignerId getSID() + { + return sid; + } + + /** + * return the version number for this objects underlying SignerInfo structure. + */ + public int getVersion() + { + return info.getVersion().getValue().intValue(); + } + + public AlgorithmIdentifier getDigestAlgorithmID() + { + return digestAlgorithm; + } + + /** + * return the object identifier for the signature. + */ + public String getDigestAlgOID() + { + return digestAlgorithm.getAlgorithm().getId(); + } + + /** + * return the signature parameters, or null if there aren't any. + */ + public byte[] getDigestAlgParams() + { + try + { + return encodeObj(digestAlgorithm.getParameters()); + } + catch (Exception e) + { + throw new RuntimeException("exception getting digest parameters " + e); + } + } + + /** + * return the content digest that was calculated during verification. + */ + public byte[] getContentDigest() + { + if (resultDigest == null) + { + throw new IllegalStateException("method can only be called after verify."); + } + + return Arrays.clone(resultDigest); + } + + /** + * return the object identifier for the signature. + */ + public String getEncryptionAlgOID() + { + return encryptionAlgorithm.getAlgorithm().getId(); + } + + /** + * return the signature/encryption algorithm parameters, or null if + * there aren't any. + */ + public byte[] getEncryptionAlgParams() + { + try + { + return encodeObj(encryptionAlgorithm.getParameters()); + } + catch (Exception e) + { + throw new RuntimeException("exception getting encryption parameters " + e); + } + } + + /** + * return a table of the signed attributes - indexed by + * the OID of the attribute. + */ + public AttributeTable getSignedAttributes() + { + if (signedAttributeSet != null && signedAttributeValues == null) + { + signedAttributeValues = new AttributeTable(signedAttributeSet); + } + + return signedAttributeValues; + } + + /** + * return a table of the unsigned attributes indexed by + * the OID of the attribute. + */ + public AttributeTable getUnsignedAttributes() + { + if (unsignedAttributeSet != null && unsignedAttributeValues == null) + { + unsignedAttributeValues = new AttributeTable(unsignedAttributeSet); + } + + return unsignedAttributeValues; + } + + /** + * return the encoded signature + */ + public byte[] getSignature() + { + return Arrays.clone(signature); + } + + /** + * Return a SignerInformationStore containing the counter signatures attached to this + * signer. If no counter signatures are present an empty store is returned. + */ + public SignerInformationStore getCounterSignatures() + { + // TODO There are several checks implied by the RFC3852 comments that are missing + + /* + The countersignature attribute MUST be an unsigned attribute; it MUST + NOT be a signed attribute, an authenticated attribute, an + unauthenticated attribute, or an unprotected attribute. + */ + AttributeTable unsignedAttributeTable = getUnsignedAttributes(); + if (unsignedAttributeTable == null) + { + return new SignerInformationStore(new ArrayList(0)); + } + + List counterSignatures = new ArrayList(); + + /* + The UnsignedAttributes syntax is defined as a SET OF Attributes. The + UnsignedAttributes in a signerInfo may include multiple instances of + the countersignature attribute. + */ + ASN1EncodableVector allCSAttrs = unsignedAttributeTable.getAll(CMSAttributes.counterSignature); + + for (int i = 0; i < allCSAttrs.size(); ++i) + { + Attribute counterSignatureAttribute = (Attribute)allCSAttrs.get(i); + + /* + A countersignature attribute can have multiple attribute values. The + syntax is defined as a SET OF AttributeValue, and there MUST be one + or more instances of AttributeValue present. + */ + ASN1Set values = counterSignatureAttribute.getAttrValues(); + if (values.size() < 1) + { + // TODO Throw an appropriate exception? + } + + for (Enumeration en = values.getObjects(); en.hasMoreElements();) + { + /* + Countersignature values have the same meaning as SignerInfo values + for ordinary signatures, except that: + + 1. The signedAttributes field MUST NOT contain a content-type + attribute; there is no content type for countersignatures. + + 2. The signedAttributes field MUST contain a message-digest + attribute if it contains any other attributes. + + 3. The input to the message-digesting process is the contents + octets of the DER encoding of the signatureValue field of the + SignerInfo value with which the attribute is associated. + */ + SignerInfo si = SignerInfo.getInstance(en.nextElement()); + + counterSignatures.add(new SignerInformation(si, null, new CMSProcessableByteArray(getSignature()), null)); + } + } + + return new SignerInformationStore(counterSignatures); + } + + /** + * return the DER encoding of the signed attributes. + * @throws IOException if an encoding error occurs. + */ + public byte[] getEncodedSignedAttributes() + throws IOException + { + if (signedAttributeSet != null) + { + return signedAttributeSet.getEncoded(); + } + + return null; + } + + private boolean doVerify( + SignerInformationVerifier verifier) + throws CMSException + { + String encName = CMSSignedHelper.INSTANCE.getEncryptionAlgName(this.getEncryptionAlgOID()); + ContentVerifier contentVerifier; + + try + { + contentVerifier = verifier.getContentVerifier(encryptionAlgorithm, info.getDigestAlgorithm()); + } + catch (OperatorCreationException e) + { + throw new CMSException("can't create content verifier: " + e.getMessage(), e); + } + + try + { + OutputStream sigOut = contentVerifier.getOutputStream(); + + if (resultDigest == null) + { + DigestCalculator calc = verifier.getDigestCalculator(this.getDigestAlgorithmID()); + if (content != null) + { + OutputStream digOut = calc.getOutputStream(); + + if (signedAttributeSet == null) + { + if (contentVerifier instanceof RawContentVerifier) + { + content.write(digOut); + } + else + { + OutputStream cOut = new TeeOutputStream(digOut, sigOut); + + content.write(cOut); + + cOut.close(); + } + } + else + { + content.write(digOut); + sigOut.write(this.getEncodedSignedAttributes()); + } + + digOut.close(); + } + else if (signedAttributeSet != null) + { + sigOut.write(this.getEncodedSignedAttributes()); + } + else + { + // TODO Get rid of this exception and just treat content==null as empty not missing? + throw new CMSException("data not encapsulated in signature - use detached constructor."); + } + + resultDigest = calc.getDigest(); + } + else + { + if (signedAttributeSet == null) + { + if (content != null) + { + content.write(sigOut); + } + } + else + { + sigOut.write(this.getEncodedSignedAttributes()); + } + } + + sigOut.close(); + } + catch (IOException e) + { + throw new CMSException("can't process mime object to create signature.", e); + } + catch (OperatorCreationException e) + { + throw new CMSException("can't create digest calculator: " + e.getMessage(), e); + } + + // RFC 3852 11.1 Check the content-type attribute is correct + { + ASN1Primitive validContentType = getSingleValuedSignedAttribute( + CMSAttributes.contentType, "content-type"); + if (validContentType == null) + { + if (!isCounterSignature && signedAttributeSet != null) + { + throw new CMSException("The content-type attribute type MUST be present whenever signed attributes are present in signed-data"); + } + } + else + { + if (isCounterSignature) + { + throw new CMSException("[For counter signatures,] the signedAttributes field MUST NOT contain a content-type attribute"); + } + + if (!(validContentType instanceof ASN1ObjectIdentifier)) + { + throw new CMSException("content-type attribute value not of ASN.1 type 'OBJECT IDENTIFIER'"); + } + + ASN1ObjectIdentifier signedContentType = (ASN1ObjectIdentifier)validContentType; + + if (!signedContentType.equals(contentType)) + { + throw new CMSException("content-type attribute value does not match eContentType"); + } + } + } + + // RFC 3852 11.2 Check the message-digest attribute is correct + { + ASN1Primitive validMessageDigest = getSingleValuedSignedAttribute( + CMSAttributes.messageDigest, "message-digest"); + if (validMessageDigest == null) + { + if (signedAttributeSet != null) + { + throw new CMSException("the message-digest signed attribute type MUST be present when there are any signed attributes present"); + } + } + else + { + if (!(validMessageDigest instanceof ASN1OctetString)) + { + throw new CMSException("message-digest attribute value not of ASN.1 type 'OCTET STRING'"); + } + + ASN1OctetString signedMessageDigest = (ASN1OctetString)validMessageDigest; + + if (!Arrays.constantTimeAreEqual(resultDigest, signedMessageDigest.getOctets())) + { + throw new CMSSignerDigestMismatchException("message-digest attribute value does not match calculated value"); + } + } + } + + // RFC 3852 11.4 Validate countersignature attribute(s) + { + AttributeTable signedAttrTable = this.getSignedAttributes(); + if (signedAttrTable != null + && signedAttrTable.getAll(CMSAttributes.counterSignature).size() > 0) + { + throw new CMSException("A countersignature attribute MUST NOT be a signed attribute"); + } + + AttributeTable unsignedAttrTable = this.getUnsignedAttributes(); + if (unsignedAttrTable != null) + { + ASN1EncodableVector csAttrs = unsignedAttrTable.getAll(CMSAttributes.counterSignature); + for (int i = 0; i < csAttrs.size(); ++i) + { + Attribute csAttr = (Attribute)csAttrs.get(i); + if (csAttr.getAttrValues().size() < 1) + { + throw new CMSException("A countersignature attribute MUST contain at least one AttributeValue"); + } + + // Note: We don't recursively validate the countersignature value + } + } + } + + try + { + if (signedAttributeSet == null && resultDigest != null) + { + if (contentVerifier instanceof RawContentVerifier) + { + RawContentVerifier rawVerifier = (RawContentVerifier)contentVerifier; + + if (encName.equals("RSA")) + { + DigestInfo digInfo = new DigestInfo(new AlgorithmIdentifier(digestAlgorithm.getAlgorithm(), DERNull.INSTANCE), resultDigest); + + return rawVerifier.verify(digInfo.getEncoded(ASN1Encoding.DER), this.getSignature()); + } + + return rawVerifier.verify(resultDigest, this.getSignature()); + } + } + + return contentVerifier.verify(this.getSignature()); + } + catch (IOException e) + { + throw new CMSException("can't process mime object to create signature.", e); + } + } + + /** + * Verify that the given verifier can successfully verify the signature on + * this SignerInformation object. + * + * @param verifier a suitably configured SignerInformationVerifier. + * @return true if the signer information is verified, false otherwise. + * @throws org.spongycastle.cms.CMSVerifierCertificateNotValidException if the provider has an associated certificate and the certificate is not valid at the time given as the SignerInfo's signing time. + * @throws org.spongycastle.cms.CMSException if the verifier is unable to create a ContentVerifiers or DigestCalculators. + */ + public boolean verify(SignerInformationVerifier verifier) + throws CMSException + { + Time signingTime = getSigningTime(); // has to be validated if present. + + if (verifier.hasAssociatedCertificate()) + { + if (signingTime != null) + { + X509CertificateHolder dcv = verifier.getAssociatedCertificate(); + + if (!dcv.isValidOn(signingTime.getDate())) + { + throw new CMSVerifierCertificateNotValidException("verifier not valid at signingTime"); + } + } + } + + return doVerify(verifier); + } + + /** + * Return the underlying ASN.1 object defining this SignerInformation object. + * + * @return a SignerInfo. + */ + public SignerInfo toASN1Structure() + { + return info; + } + + private ASN1Primitive getSingleValuedSignedAttribute( + ASN1ObjectIdentifier attrOID, String printableName) + throws CMSException + { + AttributeTable unsignedAttrTable = this.getUnsignedAttributes(); + if (unsignedAttrTable != null + && unsignedAttrTable.getAll(attrOID).size() > 0) + { + throw new CMSException("The " + printableName + + " attribute MUST NOT be an unsigned attribute"); + } + + AttributeTable signedAttrTable = this.getSignedAttributes(); + if (signedAttrTable == null) + { + return null; + } + + ASN1EncodableVector v = signedAttrTable.getAll(attrOID); + switch (v.size()) + { + case 0: + return null; + case 1: + { + Attribute t = (Attribute)v.get(0); + ASN1Set attrValues = t.getAttrValues(); + if (attrValues.size() != 1) + { + throw new CMSException("A " + printableName + + " attribute MUST have a single attribute value"); + } + + return attrValues.getObjectAt(0).toASN1Primitive(); + } + default: + throw new CMSException("The SignedAttributes in a signerInfo MUST NOT include multiple instances of the " + + printableName + " attribute"); + } + } + + private Time getSigningTime() throws CMSException + { + ASN1Primitive validSigningTime = getSingleValuedSignedAttribute( + CMSAttributes.signingTime, "signing-time"); + + if (validSigningTime == null) + { + return null; + } + + try + { + return Time.getInstance(validSigningTime); + } + catch (IllegalArgumentException e) + { + throw new CMSException("signing-time attribute value not a valid 'Time' structure"); + } + } + + /** + * Return a signer information object with the passed in unsigned + * attributes replacing the ones that are current associated with + * the object passed in. + * + * @param signerInformation the signerInfo to be used as the basis. + * @param unsignedAttributes the unsigned attributes to add. + * @return a copy of the original SignerInformationObject with the changed attributes. + */ + public static SignerInformation replaceUnsignedAttributes( + SignerInformation signerInformation, + AttributeTable unsignedAttributes) + { + SignerInfo sInfo = signerInformation.info; + ASN1Set unsignedAttr = null; + + if (unsignedAttributes != null) + { + unsignedAttr = new DERSet(unsignedAttributes.toASN1EncodableVector()); + } + + return new SignerInformation( + new SignerInfo(sInfo.getSID(), sInfo.getDigestAlgorithm(), + sInfo.getAuthenticatedAttributes(), sInfo.getDigestEncryptionAlgorithm(), sInfo.getEncryptedDigest(), unsignedAttr), + signerInformation.contentType, signerInformation.content, null); + } + + /** + * Return a signer information object with passed in SignerInformationStore representing counter + * signatures attached as an unsigned attribute. + * + * @param signerInformation the signerInfo to be used as the basis. + * @param counterSigners signer info objects carrying counter signature. + * @return a copy of the original SignerInformationObject with the changed attributes. + */ + public static SignerInformation addCounterSigners( + SignerInformation signerInformation, + SignerInformationStore counterSigners) + { + // TODO Perform checks from RFC 3852 11.4 + + SignerInfo sInfo = signerInformation.info; + AttributeTable unsignedAttr = signerInformation.getUnsignedAttributes(); + ASN1EncodableVector v; + + if (unsignedAttr != null) + { + v = unsignedAttr.toASN1EncodableVector(); + } + else + { + v = new ASN1EncodableVector(); + } + + ASN1EncodableVector sigs = new ASN1EncodableVector(); + + for (Iterator it = counterSigners.getSigners().iterator(); it.hasNext();) + { + sigs.add(((SignerInformation)it.next()).toASN1Structure()); + } + + v.add(new Attribute(CMSAttributes.counterSignature, new DERSet(sigs))); + + return new SignerInformation( + new SignerInfo(sInfo.getSID(), sInfo.getDigestAlgorithm(), + sInfo.getAuthenticatedAttributes(), sInfo.getDigestEncryptionAlgorithm(), sInfo.getEncryptedDigest(), new DERSet(v)), + signerInformation.contentType, signerInformation.content, null); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/SignerInformationStore.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/SignerInformationStore.java new file mode 100644 index 000000000..df3bb9685 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/SignerInformationStore.java @@ -0,0 +1,109 @@ +package org.spongycastle.cms; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public class SignerInformationStore +{ + private List all = new ArrayList(); + private Map table = new HashMap(); + + public SignerInformationStore( + Collection signerInfos) + { + Iterator it = signerInfos.iterator(); + + while (it.hasNext()) + { + SignerInformation signer = (SignerInformation)it.next(); + SignerId sid = signer.getSID(); + + List list = (ArrayList)table.get(sid); + if (list == null) + { + list = new ArrayList(1); + table.put(sid, list); + } + + list.add(signer); + } + + this.all = new ArrayList(signerInfos); + } + + /** + * Return the first SignerInformation object that matches the + * passed in selector. Null if there are no matches. + * + * @param selector to identify a signer + * @return a single SignerInformation object. Null if none matches. + */ + public SignerInformation get( + SignerId selector) + { + Collection list = getSigners(selector); + + return list.size() == 0 ? null : (SignerInformation) list.iterator().next(); + } + + /** + * Return the number of signers in the collection. + * + * @return number of signers identified. + */ + public int size() + { + return all.size(); + } + + /** + * Return all signers in the collection + * + * @return a collection of signers. + */ + public Collection getSigners() + { + return new ArrayList(all); + } + + /** + * Return possible empty collection with signers matching the passed in SignerId + * + * @param selector a signer id to select against. + * @return a collection of SignerInformation objects. + */ + public Collection getSigners( + SignerId selector) + { + if (selector.getIssuer() != null && selector.getSubjectKeyIdentifier() != null) + { + List results = new ArrayList(); + + Collection match1 = getSigners(new SignerId(selector.getIssuer(), selector.getSerialNumber())); + + if (match1 != null) + { + results.addAll(match1); + } + + Collection match2 = getSigners(new SignerId(selector.getSubjectKeyIdentifier())); + + if (match2 != null) + { + results.addAll(match2); + } + + return results; + } + else + { + List list = (ArrayList)table.get(selector); + + return list == null ? new ArrayList() : new ArrayList(list); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/SignerInformationVerifier.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/SignerInformationVerifier.java new file mode 100644 index 000000000..ea9de675c --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/SignerInformationVerifier.java @@ -0,0 +1,50 @@ +package org.spongycastle.cms; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.operator.ContentVerifier; +import org.spongycastle.operator.ContentVerifierProvider; +import org.spongycastle.operator.DigestCalculator; +import org.spongycastle.operator.DigestCalculatorProvider; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.operator.SignatureAlgorithmIdentifierFinder; + +public class SignerInformationVerifier +{ + private ContentVerifierProvider verifierProvider; + private DigestCalculatorProvider digestProvider; + private SignatureAlgorithmIdentifierFinder sigAlgorithmFinder; + private CMSSignatureAlgorithmNameGenerator sigNameGenerator; + + public SignerInformationVerifier(CMSSignatureAlgorithmNameGenerator sigNameGenerator, SignatureAlgorithmIdentifierFinder sigAlgorithmFinder, ContentVerifierProvider verifierProvider, DigestCalculatorProvider digestProvider) + { + this.sigNameGenerator = sigNameGenerator; + this.sigAlgorithmFinder = sigAlgorithmFinder; + this.verifierProvider = verifierProvider; + this.digestProvider = digestProvider; + } + + public boolean hasAssociatedCertificate() + { + return verifierProvider.hasAssociatedCertificate(); + } + + public X509CertificateHolder getAssociatedCertificate() + { + return verifierProvider.getAssociatedCertificate(); + } + + public ContentVerifier getContentVerifier(AlgorithmIdentifier signingAlgorithm, AlgorithmIdentifier digestAlgorithm) + throws OperatorCreationException + { + String signatureName = sigNameGenerator.getSignatureName(digestAlgorithm, signingAlgorithm); + + return verifierProvider.get(sigAlgorithmFinder.find(signatureName)); + } + + public DigestCalculator getDigestCalculator(AlgorithmIdentifier algorithmIdentifier) + throws OperatorCreationException + { + return digestProvider.get(algorithmIdentifier); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/SignerInformationVerifierProvider.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/SignerInformationVerifierProvider.java new file mode 100644 index 000000000..d6e7d6e5b --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/SignerInformationVerifierProvider.java @@ -0,0 +1,16 @@ +package org.spongycastle.cms; + +import org.spongycastle.operator.OperatorCreationException; + +public interface SignerInformationVerifierProvider +{ + /** + * Return a SignerInformationVerifierProvider suitable for the passed in SID. + * + * @param sid the SignerId we are trying to match for. + * @return a verifier if one is available, null otherwise. + * @throws OperatorCreationException if creation of the verifier fails when it should suceed. + */ + public SignerInformationVerifier get(SignerId sid) + throws OperatorCreationException; +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/SimpleAttributeTableGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/SimpleAttributeTableGenerator.java new file mode 100644 index 000000000..25c55b785 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/SimpleAttributeTableGenerator.java @@ -0,0 +1,25 @@ +package org.spongycastle.cms; + +import org.spongycastle.asn1.cms.AttributeTable; + +import java.util.Map; + +/** + * Basic generator that just returns a preconstructed attribute table + */ +public class SimpleAttributeTableGenerator + implements CMSAttributeTableGenerator +{ + private final AttributeTable attributes; + + public SimpleAttributeTableGenerator( + AttributeTable attributes) + { + this.attributes = attributes; + } + + public AttributeTable getAttributes(Map parameters) + { + return attributes; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcCMSContentEncryptorBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcCMSContentEncryptorBuilder.java new file mode 100644 index 000000000..22f79fb77 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcCMSContentEncryptorBuilder.java @@ -0,0 +1,124 @@ +package org.spongycastle.cms.bc; + +import java.io.OutputStream; +import java.security.SecureRandom; +import java.util.HashMap; +import java.util.Map; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cms.CMSAlgorithm; +import org.spongycastle.cms.CMSException; +import org.spongycastle.crypto.BufferedBlockCipher; +import org.spongycastle.crypto.CipherKeyGenerator; +import org.spongycastle.crypto.StreamCipher; +import org.spongycastle.crypto.io.CipherOutputStream; +import org.spongycastle.crypto.params.KeyParameter; +import org.spongycastle.operator.GenericKey; +import org.spongycastle.operator.OutputEncryptor; +import org.spongycastle.util.Integers; + +public class BcCMSContentEncryptorBuilder +{ + private static Map keySizes = new HashMap(); + + static + { + keySizes.put(CMSAlgorithm.AES128_CBC, Integers.valueOf(128)); + keySizes.put(CMSAlgorithm.AES192_CBC, Integers.valueOf(192)); + keySizes.put(CMSAlgorithm.AES256_CBC, Integers.valueOf(256)); + + keySizes.put(CMSAlgorithm.CAMELLIA128_CBC, Integers.valueOf(128)); + keySizes.put(CMSAlgorithm.CAMELLIA192_CBC, Integers.valueOf(192)); + keySizes.put(CMSAlgorithm.CAMELLIA256_CBC, Integers.valueOf(256)); + } + + private static int getKeySize(ASN1ObjectIdentifier oid) + { + Integer size = (Integer)keySizes.get(oid); + + if (size != null) + { + return size.intValue(); + } + + return -1; + } + + private final ASN1ObjectIdentifier encryptionOID; + private final int keySize; + + private EnvelopedDataHelper helper = new EnvelopedDataHelper(); + private SecureRandom random; + + public BcCMSContentEncryptorBuilder(ASN1ObjectIdentifier encryptionOID) + { + this(encryptionOID, getKeySize(encryptionOID)); + } + + public BcCMSContentEncryptorBuilder(ASN1ObjectIdentifier encryptionOID, int keySize) + { + this.encryptionOID = encryptionOID; + this.keySize = keySize; + } + + public BcCMSContentEncryptorBuilder setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + public OutputEncryptor build() + throws CMSException + { + return new CMSOutputEncryptor(encryptionOID, keySize, random); + } + + private class CMSOutputEncryptor + implements OutputEncryptor + { + private KeyParameter encKey; + private AlgorithmIdentifier algorithmIdentifier; + private Object cipher; + + CMSOutputEncryptor(ASN1ObjectIdentifier encryptionOID, int keySize, SecureRandom random) + throws CMSException + { + if (random == null) + { + random = new SecureRandom(); + } + + CipherKeyGenerator keyGen = helper.createKeyGenerator(encryptionOID, random); + + encKey = new KeyParameter(keyGen.generateKey()); + + algorithmIdentifier = helper.generateAlgorithmIdentifier(encryptionOID, encKey, random); + + cipher = helper.createContentCipher(true, encKey, algorithmIdentifier); + } + + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return algorithmIdentifier; + } + + public OutputStream getOutputStream(OutputStream dOut) + { + if (cipher instanceof BufferedBlockCipher) + { + return new CipherOutputStream(dOut, (BufferedBlockCipher)cipher); + } + else + { + return new CipherOutputStream(dOut, (StreamCipher)cipher); + } + } + + public GenericKey getKey() + { + return new GenericKey(algorithmIdentifier, encKey.getKey()); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcKEKEnvelopedRecipient.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcKEKEnvelopedRecipient.java new file mode 100644 index 000000000..afde8a02c --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcKEKEnvelopedRecipient.java @@ -0,0 +1,49 @@ +package org.spongycastle.cms.bc; + +import java.io.InputStream; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cms.CMSException; +import org.spongycastle.cms.RecipientOperator; +import org.spongycastle.crypto.BufferedBlockCipher; +import org.spongycastle.crypto.StreamCipher; +import org.spongycastle.crypto.params.KeyParameter; +import org.spongycastle.operator.InputDecryptor; +import org.spongycastle.operator.bc.BcSymmetricKeyUnwrapper; + +public class BcKEKEnvelopedRecipient + extends BcKEKRecipient +{ + public BcKEKEnvelopedRecipient(BcSymmetricKeyUnwrapper unwrapper) + { + super(unwrapper); + } + + public RecipientOperator getRecipientOperator(AlgorithmIdentifier keyEncryptionAlgorithm, final AlgorithmIdentifier contentEncryptionAlgorithm, byte[] encryptedContentEncryptionKey) + throws CMSException + { + KeyParameter secretKey = (KeyParameter)extractSecretKey(keyEncryptionAlgorithm, contentEncryptionAlgorithm, encryptedContentEncryptionKey); + + final Object dataCipher = EnvelopedDataHelper.createContentCipher(false, secretKey, contentEncryptionAlgorithm); + + return new RecipientOperator(new InputDecryptor() + { + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return contentEncryptionAlgorithm; + } + + public InputStream getInputStream(InputStream dataOut) + { + if (dataCipher instanceof BufferedBlockCipher) + { + return new org.spongycastle.crypto.io.CipherInputStream(dataOut, (BufferedBlockCipher)dataCipher); + } + else + { + return new org.spongycastle.crypto.io.CipherInputStream(dataOut, (StreamCipher)dataCipher); + } + } + }); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcKEKRecipient.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcKEKRecipient.java new file mode 100644 index 000000000..066deaa62 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcKEKRecipient.java @@ -0,0 +1,33 @@ +package org.spongycastle.cms.bc; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cms.CMSException; +import org.spongycastle.cms.KEKRecipient; +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.operator.OperatorException; +import org.spongycastle.operator.SymmetricKeyUnwrapper; +import org.spongycastle.operator.bc.BcSymmetricKeyUnwrapper; + +public abstract class BcKEKRecipient + implements KEKRecipient +{ + private SymmetricKeyUnwrapper unwrapper; + + public BcKEKRecipient(BcSymmetricKeyUnwrapper unwrapper) + { + this.unwrapper = unwrapper; + } + + protected CipherParameters extractSecretKey(AlgorithmIdentifier keyEncryptionAlgorithm, AlgorithmIdentifier contentEncryptionAlgorithm, byte[] encryptedContentEncryptionKey) + throws CMSException + { + try + { + return CMSUtils.getBcKey(unwrapper.generateUnwrappedKey(contentEncryptionAlgorithm, encryptedContentEncryptionKey)); + } + catch (OperatorException e) + { + throw new CMSException("exception unwrapping key: " + e.getMessage(), e); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcKEKRecipientInfoGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcKEKRecipientInfoGenerator.java new file mode 100644 index 000000000..2a2ad9e79 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcKEKRecipientInfoGenerator.java @@ -0,0 +1,19 @@ +package org.spongycastle.cms.bc; + +import org.spongycastle.asn1.cms.KEKIdentifier; +import org.spongycastle.cms.KEKRecipientInfoGenerator; +import org.spongycastle.operator.bc.BcSymmetricKeyWrapper; + +public class BcKEKRecipientInfoGenerator + extends KEKRecipientInfoGenerator +{ + public BcKEKRecipientInfoGenerator(KEKIdentifier kekIdentifier, BcSymmetricKeyWrapper kekWrapper) + { + super(kekIdentifier, kekWrapper); + } + + public BcKEKRecipientInfoGenerator(byte[] keyIdentifier, BcSymmetricKeyWrapper kekWrapper) + { + this(new KEKIdentifier(keyIdentifier, null, null), kekWrapper); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcKeyTransRecipient.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcKeyTransRecipient.java new file mode 100644 index 000000000..ab50269d7 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcKeyTransRecipient.java @@ -0,0 +1,36 @@ +package org.spongycastle.cms.bc; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cms.CMSException; +import org.spongycastle.cms.KeyTransRecipient; +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.operator.AsymmetricKeyUnwrapper; +import org.spongycastle.operator.OperatorException; +import org.spongycastle.operator.bc.BcRSAAsymmetricKeyUnwrapper; + +public abstract class BcKeyTransRecipient + implements KeyTransRecipient +{ + private AsymmetricKeyParameter recipientKey; + + public BcKeyTransRecipient(AsymmetricKeyParameter recipientKey) + { + this.recipientKey = recipientKey; + } + + protected CipherParameters extractSecretKey(AlgorithmIdentifier keyEncryptionAlgorithm, AlgorithmIdentifier encryptedKeyAlgorithm, byte[] encryptedEncryptionKey) + throws CMSException + { + AsymmetricKeyUnwrapper unwrapper = new BcRSAAsymmetricKeyUnwrapper(keyEncryptionAlgorithm, recipientKey); + + try + { + return CMSUtils.getBcKey(unwrapper.generateUnwrappedKey(encryptedKeyAlgorithm, encryptedEncryptionKey)); + } + catch (OperatorException e) + { + throw new CMSException("exception unwrapping key: " + e.getMessage(), e); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcKeyTransRecipientInfoGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcKeyTransRecipientInfoGenerator.java new file mode 100644 index 000000000..c987d5a26 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcKeyTransRecipientInfoGenerator.java @@ -0,0 +1,20 @@ +package org.spongycastle.cms.bc; + +import org.spongycastle.asn1.cms.IssuerAndSerialNumber; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.cms.KeyTransRecipientInfoGenerator; +import org.spongycastle.operator.bc.BcAsymmetricKeyWrapper; + +public abstract class BcKeyTransRecipientInfoGenerator + extends KeyTransRecipientInfoGenerator +{ + public BcKeyTransRecipientInfoGenerator(X509CertificateHolder recipientCert, BcAsymmetricKeyWrapper wrapper) + { + super(new IssuerAndSerialNumber(recipientCert.toASN1Structure()), wrapper); + } + + public BcKeyTransRecipientInfoGenerator(byte[] subjectKeyIdentifier, BcAsymmetricKeyWrapper wrapper) + { + super(subjectKeyIdentifier, wrapper); + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcPasswordEnvelopedRecipient.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcPasswordEnvelopedRecipient.java new file mode 100644 index 000000000..0b9a529ff --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcPasswordEnvelopedRecipient.java @@ -0,0 +1,49 @@ +package org.spongycastle.cms.bc; + +import java.io.InputStream; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cms.CMSException; +import org.spongycastle.cms.RecipientOperator; +import org.spongycastle.crypto.BufferedBlockCipher; +import org.spongycastle.crypto.StreamCipher; +import org.spongycastle.crypto.io.CipherInputStream; +import org.spongycastle.crypto.params.KeyParameter; +import org.spongycastle.operator.InputDecryptor; + +public class BcPasswordEnvelopedRecipient + extends BcPasswordRecipient +{ + public BcPasswordEnvelopedRecipient(char[] password) + { + super(password); + } + + public RecipientOperator getRecipientOperator(AlgorithmIdentifier keyEncryptionAlgorithm, final AlgorithmIdentifier contentEncryptionAlgorithm, byte[] derivedKey, byte[] encryptedContentEncryptionKey) + throws CMSException + { + KeyParameter secretKey = extractSecretKey(keyEncryptionAlgorithm, contentEncryptionAlgorithm, derivedKey, encryptedContentEncryptionKey); + + final Object dataCipher = EnvelopedDataHelper.createContentCipher(false, secretKey, contentEncryptionAlgorithm); + + return new RecipientOperator(new InputDecryptor() + { + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return contentEncryptionAlgorithm; + } + + public InputStream getInputStream(InputStream dataOut) + { + if (dataCipher instanceof BufferedBlockCipher) + { + return new CipherInputStream(dataOut, (BufferedBlockCipher)dataCipher); + } + else + { + return new CipherInputStream(dataOut, (StreamCipher)dataCipher); + } + } + }); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcPasswordRecipient.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcPasswordRecipient.java new file mode 100644 index 000000000..5317bb5ff --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcPasswordRecipient.java @@ -0,0 +1,61 @@ +package org.spongycastle.cms.bc; + +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cms.CMSException; +import org.spongycastle.cms.PasswordRecipient; +import org.spongycastle.crypto.InvalidCipherTextException; +import org.spongycastle.crypto.Wrapper; +import org.spongycastle.crypto.params.KeyParameter; +import org.spongycastle.crypto.params.ParametersWithIV; + +/** + * the RecipientInfo class for a recipient who has been sent a message + * encrypted using a password. + */ +public abstract class BcPasswordRecipient + implements PasswordRecipient +{ + private int schemeID = PasswordRecipient.PKCS5_SCHEME2_UTF8; + private char[] password; + + BcPasswordRecipient( + char[] password) + { + this.password = password; + } + + public BcPasswordRecipient setPasswordConversionScheme(int schemeID) + { + this.schemeID = schemeID; + + return this; + } + + protected KeyParameter extractSecretKey(AlgorithmIdentifier keyEncryptionAlgorithm, AlgorithmIdentifier contentEncryptionAlgorithm, byte[] derivedKey, byte[] encryptedContentEncryptionKey) + throws CMSException + { + Wrapper keyEncryptionCipher = EnvelopedDataHelper.createRFC3211Wrapper(keyEncryptionAlgorithm.getAlgorithm()); + + keyEncryptionCipher.init(false, new ParametersWithIV(new KeyParameter(derivedKey), ASN1OctetString.getInstance(keyEncryptionAlgorithm.getParameters()).getOctets())); + + try + { + return new KeyParameter(keyEncryptionCipher.unwrap(encryptedContentEncryptionKey, 0, encryptedContentEncryptionKey.length)); + } + catch (InvalidCipherTextException e) + { + throw new CMSException("unable to unwrap key: " + e.getMessage(), e); + } + } + + public int getPasswordConversionScheme() + { + return schemeID; + } + + public char[] getPassword() + { + return password; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcPasswordRecipientInfoGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcPasswordRecipientInfoGenerator.java new file mode 100644 index 000000000..c1559be79 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcPasswordRecipientInfoGenerator.java @@ -0,0 +1,31 @@ +package org.spongycastle.cms.bc; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cms.CMSException; +import org.spongycastle.cms.PasswordRecipientInfoGenerator; +import org.spongycastle.crypto.Wrapper; +import org.spongycastle.crypto.params.KeyParameter; +import org.spongycastle.crypto.params.ParametersWithIV; +import org.spongycastle.operator.GenericKey; + +public class BcPasswordRecipientInfoGenerator + extends PasswordRecipientInfoGenerator +{ + public BcPasswordRecipientInfoGenerator(ASN1ObjectIdentifier kekAlgorithm, char[] password) + { + super(kekAlgorithm, password); + } + + public byte[] generateEncryptedBytes(AlgorithmIdentifier keyEncryptionAlgorithm, byte[] derivedKey, GenericKey contentEncryptionKey) + throws CMSException + { + byte[] contentEncryptionKeySpec = ((KeyParameter)CMSUtils.getBcKey(contentEncryptionKey)).getKey(); + Wrapper keyEncryptionCipher = EnvelopedDataHelper.createRFC3211Wrapper(keyEncryptionAlgorithm.getAlgorithm()); + + keyEncryptionCipher.init(true, new ParametersWithIV(new KeyParameter(derivedKey), ASN1OctetString.getInstance(keyEncryptionAlgorithm.getParameters()).getOctets())); + + return keyEncryptionCipher.wrap(contentEncryptionKeySpec, 0, contentEncryptionKeySpec.length); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcRSAKeyTransEnvelopedRecipient.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcRSAKeyTransEnvelopedRecipient.java new file mode 100644 index 000000000..601de8413 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcRSAKeyTransEnvelopedRecipient.java @@ -0,0 +1,50 @@ +package org.spongycastle.cms.bc; + +import java.io.InputStream; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cms.CMSException; +import org.spongycastle.cms.RecipientOperator; +import org.spongycastle.crypto.BufferedBlockCipher; +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.StreamCipher; +import org.spongycastle.crypto.io.CipherInputStream; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.operator.InputDecryptor; + +public class BcRSAKeyTransEnvelopedRecipient + extends BcKeyTransRecipient +{ + public BcRSAKeyTransEnvelopedRecipient(AsymmetricKeyParameter key) + { + super(key); + } + + public RecipientOperator getRecipientOperator(AlgorithmIdentifier keyEncryptionAlgorithm, final AlgorithmIdentifier contentEncryptionAlgorithm, byte[] encryptedContentEncryptionKey) + throws CMSException + { + CipherParameters secretKey = extractSecretKey(keyEncryptionAlgorithm, contentEncryptionAlgorithm, encryptedContentEncryptionKey); + + final Object dataCipher = EnvelopedDataHelper.createContentCipher(false, secretKey, contentEncryptionAlgorithm); + + return new RecipientOperator(new InputDecryptor() + { + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return contentEncryptionAlgorithm; + } + + public InputStream getInputStream(InputStream dataIn) + { + if (dataCipher instanceof BufferedBlockCipher) + { + return new CipherInputStream(dataIn, (BufferedBlockCipher)dataCipher); + } + else + { + return new CipherInputStream(dataIn, (StreamCipher)dataCipher); + } + } + }); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcRSAKeyTransRecipientInfoGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcRSAKeyTransRecipientInfoGenerator.java new file mode 100644 index 000000000..09f7f8d0f --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcRSAKeyTransRecipientInfoGenerator.java @@ -0,0 +1,23 @@ +package org.spongycastle.cms.bc; + +import java.io.IOException; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.operator.bc.BcRSAAsymmetricKeyWrapper; + +public class BcRSAKeyTransRecipientInfoGenerator + extends BcKeyTransRecipientInfoGenerator +{ + public BcRSAKeyTransRecipientInfoGenerator(byte[] subjectKeyIdentifier, AlgorithmIdentifier encAlgId, AsymmetricKeyParameter publicKey) + { + super(subjectKeyIdentifier, new BcRSAAsymmetricKeyWrapper(encAlgId, publicKey)); + } + + public BcRSAKeyTransRecipientInfoGenerator(X509CertificateHolder recipientCert) + throws IOException + { + super(recipientCert, new BcRSAAsymmetricKeyWrapper(recipientCert.getSubjectPublicKeyInfo().getAlgorithmId(), recipientCert.getSubjectPublicKeyInfo())); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcRSASignerInfoVerifierBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcRSASignerInfoVerifierBuilder.java new file mode 100644 index 000000000..26028e1af --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/BcRSASignerInfoVerifierBuilder.java @@ -0,0 +1,39 @@ +package org.spongycastle.cms.bc; + +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.cms.CMSSignatureAlgorithmNameGenerator; +import org.spongycastle.cms.SignerInformationVerifier; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.operator.DigestAlgorithmIdentifierFinder; +import org.spongycastle.operator.DigestCalculatorProvider; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.operator.SignatureAlgorithmIdentifierFinder; +import org.spongycastle.operator.bc.BcRSAContentVerifierProviderBuilder; + +public class BcRSASignerInfoVerifierBuilder +{ + private BcRSAContentVerifierProviderBuilder contentVerifierProviderBuilder; + private DigestCalculatorProvider digestCalculatorProvider; + private CMSSignatureAlgorithmNameGenerator sigAlgNameGen; + private SignatureAlgorithmIdentifierFinder sigAlgIdFinder; + + public BcRSASignerInfoVerifierBuilder(CMSSignatureAlgorithmNameGenerator sigAlgNameGen, SignatureAlgorithmIdentifierFinder sigAlgIdFinder, DigestAlgorithmIdentifierFinder digestAlgorithmFinder, DigestCalculatorProvider digestCalculatorProvider) + { + this.sigAlgNameGen = sigAlgNameGen; + this.sigAlgIdFinder = sigAlgIdFinder; + this.contentVerifierProviderBuilder = new BcRSAContentVerifierProviderBuilder(digestAlgorithmFinder); + this.digestCalculatorProvider = digestCalculatorProvider; + } + + public SignerInformationVerifier build(X509CertificateHolder certHolder) + throws OperatorCreationException + { + return new SignerInformationVerifier(sigAlgNameGen, sigAlgIdFinder, contentVerifierProviderBuilder.build(certHolder), digestCalculatorProvider); + } + + public SignerInformationVerifier build(AsymmetricKeyParameter pubKey) + throws OperatorCreationException + { + return new SignerInformationVerifier(sigAlgNameGen, sigAlgIdFinder, contentVerifierProviderBuilder.build(pubKey), digestCalculatorProvider); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/CMSUtils.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/CMSUtils.java new file mode 100644 index 000000000..90035ec1c --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/CMSUtils.java @@ -0,0 +1,23 @@ +package org.spongycastle.cms.bc; + +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.params.KeyParameter; +import org.spongycastle.operator.GenericKey; + +class CMSUtils +{ + static CipherParameters getBcKey(GenericKey key) + { + if (key.getRepresentation() instanceof CipherParameters) + { + return (CipherParameters)key.getRepresentation(); + } + + if (key.getRepresentation() instanceof byte[]) + { + return new KeyParameter((byte[])key.getRepresentation()); + } + + throw new IllegalArgumentException("unknown generic key type"); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/EnvelopedDataHelper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/EnvelopedDataHelper.java new file mode 100644 index 000000000..db65fd098 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/bc/EnvelopedDataHelper.java @@ -0,0 +1,378 @@ +package org.spongycastle.cms.bc; + +import java.security.SecureRandom; +import java.util.HashMap; +import java.util.Map; + +import org.spongycastle.asn1.ASN1Null; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.DERNull; +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.kisa.KISAObjectIdentifiers; +import org.spongycastle.asn1.misc.CAST5CBCParameters; +import org.spongycastle.asn1.nist.NISTObjectIdentifiers; +import org.spongycastle.asn1.ntt.NTTObjectIdentifiers; +import org.spongycastle.asn1.oiw.OIWObjectIdentifiers; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.pkcs.RC2CBCParameter; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cms.CMSAlgorithm; +import org.spongycastle.cms.CMSException; +import org.spongycastle.crypto.BlockCipher; +import org.spongycastle.crypto.BufferedBlockCipher; +import org.spongycastle.crypto.CipherKeyGenerator; +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.KeyGenerationParameters; +import org.spongycastle.crypto.StreamCipher; +import org.spongycastle.crypto.Wrapper; +import org.spongycastle.crypto.engines.AESEngine; +import org.spongycastle.crypto.engines.DESEngine; +import org.spongycastle.crypto.engines.DESedeEngine; +import org.spongycastle.crypto.engines.RC2Engine; +import org.spongycastle.crypto.engines.RC4Engine; +import org.spongycastle.crypto.engines.RFC3211WrapEngine; +import org.spongycastle.crypto.generators.DESKeyGenerator; +import org.spongycastle.crypto.generators.DESedeKeyGenerator; +import org.spongycastle.crypto.modes.CBCBlockCipher; +import org.spongycastle.crypto.paddings.PKCS7Padding; +import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher; +import org.spongycastle.crypto.params.KeyParameter; +import org.spongycastle.crypto.params.ParametersWithIV; +import org.spongycastle.crypto.params.RC2Parameters; + +class EnvelopedDataHelper +{ + protected static final Map BASE_CIPHER_NAMES = new HashMap(); + protected static final Map CIPHER_ALG_NAMES = new HashMap(); + protected static final Map MAC_ALG_NAMES = new HashMap(); + + static + { + BASE_CIPHER_NAMES.put(CMSAlgorithm.DES_EDE3_CBC, "DESEDE"); + BASE_CIPHER_NAMES.put(CMSAlgorithm.AES128_CBC, "AES"); + BASE_CIPHER_NAMES.put(CMSAlgorithm.AES192_CBC, "AES"); + BASE_CIPHER_NAMES.put(CMSAlgorithm.AES256_CBC, "AES"); + + CIPHER_ALG_NAMES.put(CMSAlgorithm.DES_EDE3_CBC, "DESEDE/CBC/PKCS5Padding"); + CIPHER_ALG_NAMES.put(CMSAlgorithm.AES128_CBC, "AES/CBC/PKCS5Padding"); + CIPHER_ALG_NAMES.put(CMSAlgorithm.AES192_CBC, "AES/CBC/PKCS5Padding"); + CIPHER_ALG_NAMES.put(CMSAlgorithm.AES256_CBC, "AES/CBC/PKCS5Padding"); + CIPHER_ALG_NAMES.put(new ASN1ObjectIdentifier(PKCSObjectIdentifiers.rsaEncryption.getId()), "RSA/ECB/PKCS1Padding"); + CIPHER_ALG_NAMES.put(CMSAlgorithm.CAST5_CBC, "CAST5/CBC/PKCS5Padding"); + CIPHER_ALG_NAMES.put(CMSAlgorithm.CAMELLIA128_CBC, "Camellia/CBC/PKCS5Padding"); + CIPHER_ALG_NAMES.put(CMSAlgorithm.CAMELLIA192_CBC, "Camellia/CBC/PKCS5Padding"); + CIPHER_ALG_NAMES.put(CMSAlgorithm.CAMELLIA256_CBC, "Camellia/CBC/PKCS5Padding"); + CIPHER_ALG_NAMES.put(CMSAlgorithm.SEED_CBC, "SEED/CBC/PKCS5Padding"); + + MAC_ALG_NAMES.put(CMSAlgorithm.DES_EDE3_CBC, "DESEDEMac"); + MAC_ALG_NAMES.put(CMSAlgorithm.AES128_CBC, "AESMac"); + MAC_ALG_NAMES.put(CMSAlgorithm.AES192_CBC, "AESMac"); + MAC_ALG_NAMES.put(CMSAlgorithm.AES256_CBC, "AESMac"); + MAC_ALG_NAMES.put(CMSAlgorithm.RC2_CBC, "RC2Mac"); + } + + private static final short[] rc2Table = { + 0xbd, 0x56, 0xea, 0xf2, 0xa2, 0xf1, 0xac, 0x2a, 0xb0, 0x93, 0xd1, 0x9c, 0x1b, 0x33, 0xfd, 0xd0, + 0x30, 0x04, 0xb6, 0xdc, 0x7d, 0xdf, 0x32, 0x4b, 0xf7, 0xcb, 0x45, 0x9b, 0x31, 0xbb, 0x21, 0x5a, + 0x41, 0x9f, 0xe1, 0xd9, 0x4a, 0x4d, 0x9e, 0xda, 0xa0, 0x68, 0x2c, 0xc3, 0x27, 0x5f, 0x80, 0x36, + 0x3e, 0xee, 0xfb, 0x95, 0x1a, 0xfe, 0xce, 0xa8, 0x34, 0xa9, 0x13, 0xf0, 0xa6, 0x3f, 0xd8, 0x0c, + 0x78, 0x24, 0xaf, 0x23, 0x52, 0xc1, 0x67, 0x17, 0xf5, 0x66, 0x90, 0xe7, 0xe8, 0x07, 0xb8, 0x60, + 0x48, 0xe6, 0x1e, 0x53, 0xf3, 0x92, 0xa4, 0x72, 0x8c, 0x08, 0x15, 0x6e, 0x86, 0x00, 0x84, 0xfa, + 0xf4, 0x7f, 0x8a, 0x42, 0x19, 0xf6, 0xdb, 0xcd, 0x14, 0x8d, 0x50, 0x12, 0xba, 0x3c, 0x06, 0x4e, + 0xec, 0xb3, 0x35, 0x11, 0xa1, 0x88, 0x8e, 0x2b, 0x94, 0x99, 0xb7, 0x71, 0x74, 0xd3, 0xe4, 0xbf, + 0x3a, 0xde, 0x96, 0x0e, 0xbc, 0x0a, 0xed, 0x77, 0xfc, 0x37, 0x6b, 0x03, 0x79, 0x89, 0x62, 0xc6, + 0xd7, 0xc0, 0xd2, 0x7c, 0x6a, 0x8b, 0x22, 0xa3, 0x5b, 0x05, 0x5d, 0x02, 0x75, 0xd5, 0x61, 0xe3, + 0x18, 0x8f, 0x55, 0x51, 0xad, 0x1f, 0x0b, 0x5e, 0x85, 0xe5, 0xc2, 0x57, 0x63, 0xca, 0x3d, 0x6c, + 0xb4, 0xc5, 0xcc, 0x70, 0xb2, 0x91, 0x59, 0x0d, 0x47, 0x20, 0xc8, 0x4f, 0x58, 0xe0, 0x01, 0xe2, + 0x16, 0x38, 0xc4, 0x6f, 0x3b, 0x0f, 0x65, 0x46, 0xbe, 0x7e, 0x2d, 0x7b, 0x82, 0xf9, 0x40, 0xb5, + 0x1d, 0x73, 0xf8, 0xeb, 0x26, 0xc7, 0x87, 0x97, 0x25, 0x54, 0xb1, 0x28, 0xaa, 0x98, 0x9d, 0xa5, + 0x64, 0x6d, 0x7a, 0xd4, 0x10, 0x81, 0x44, 0xef, 0x49, 0xd6, 0xae, 0x2e, 0xdd, 0x76, 0x5c, 0x2f, + 0xa7, 0x1c, 0xc9, 0x09, 0x69, 0x9a, 0x83, 0xcf, 0x29, 0x39, 0xb9, 0xe9, 0x4c, 0xff, 0x43, 0xab + }; + + private static final short[] rc2Ekb = { + 0x5d, 0xbe, 0x9b, 0x8b, 0x11, 0x99, 0x6e, 0x4d, 0x59, 0xf3, 0x85, 0xa6, 0x3f, 0xb7, 0x83, 0xc5, + 0xe4, 0x73, 0x6b, 0x3a, 0x68, 0x5a, 0xc0, 0x47, 0xa0, 0x64, 0x34, 0x0c, 0xf1, 0xd0, 0x52, 0xa5, + 0xb9, 0x1e, 0x96, 0x43, 0x41, 0xd8, 0xd4, 0x2c, 0xdb, 0xf8, 0x07, 0x77, 0x2a, 0xca, 0xeb, 0xef, + 0x10, 0x1c, 0x16, 0x0d, 0x38, 0x72, 0x2f, 0x89, 0xc1, 0xf9, 0x80, 0xc4, 0x6d, 0xae, 0x30, 0x3d, + 0xce, 0x20, 0x63, 0xfe, 0xe6, 0x1a, 0xc7, 0xb8, 0x50, 0xe8, 0x24, 0x17, 0xfc, 0x25, 0x6f, 0xbb, + 0x6a, 0xa3, 0x44, 0x53, 0xd9, 0xa2, 0x01, 0xab, 0xbc, 0xb6, 0x1f, 0x98, 0xee, 0x9a, 0xa7, 0x2d, + 0x4f, 0x9e, 0x8e, 0xac, 0xe0, 0xc6, 0x49, 0x46, 0x29, 0xf4, 0x94, 0x8a, 0xaf, 0xe1, 0x5b, 0xc3, + 0xb3, 0x7b, 0x57, 0xd1, 0x7c, 0x9c, 0xed, 0x87, 0x40, 0x8c, 0xe2, 0xcb, 0x93, 0x14, 0xc9, 0x61, + 0x2e, 0xe5, 0xcc, 0xf6, 0x5e, 0xa8, 0x5c, 0xd6, 0x75, 0x8d, 0x62, 0x95, 0x58, 0x69, 0x76, 0xa1, + 0x4a, 0xb5, 0x55, 0x09, 0x78, 0x33, 0x82, 0xd7, 0xdd, 0x79, 0xf5, 0x1b, 0x0b, 0xde, 0x26, 0x21, + 0x28, 0x74, 0x04, 0x97, 0x56, 0xdf, 0x3c, 0xf0, 0x37, 0x39, 0xdc, 0xff, 0x06, 0xa4, 0xea, 0x42, + 0x08, 0xda, 0xb4, 0x71, 0xb0, 0xcf, 0x12, 0x7a, 0x4e, 0xfa, 0x6c, 0x1d, 0x84, 0x00, 0xc8, 0x7f, + 0x91, 0x45, 0xaa, 0x2b, 0xc2, 0xb1, 0x8f, 0xd5, 0xba, 0xf2, 0xad, 0x19, 0xb2, 0x67, 0x36, 0xf7, + 0x0f, 0x0a, 0x92, 0x7d, 0xe3, 0x9d, 0xe9, 0x90, 0x3e, 0x23, 0x27, 0x66, 0x13, 0xec, 0x81, 0x15, + 0xbd, 0x22, 0xbf, 0x9f, 0x7e, 0xa9, 0x51, 0x4b, 0x4c, 0xfb, 0x02, 0xd3, 0x70, 0x86, 0x31, 0xe7, + 0x3b, 0x05, 0x03, 0x54, 0x60, 0x48, 0x65, 0x18, 0xd2, 0xcd, 0x5f, 0x32, 0x88, 0x0e, 0x35, 0xfd + }; + + EnvelopedDataHelper() + { + } + + String getBaseCipherName(ASN1ObjectIdentifier algorithm) + { + String name = (String)BASE_CIPHER_NAMES.get(algorithm); + + if (name == null) + { + return algorithm.getId(); + } + + return name; + } + + static BufferedBlockCipher createCipher(ASN1ObjectIdentifier algorithm) + throws CMSException + { + BlockCipher cipher; + + if (NISTObjectIdentifiers.id_aes128_CBC.equals(algorithm) + || NISTObjectIdentifiers.id_aes192_CBC.equals(algorithm) + || NISTObjectIdentifiers.id_aes256_CBC.equals(algorithm)) + { + cipher = new CBCBlockCipher(new AESEngine()); + } + else if (PKCSObjectIdentifiers.des_EDE3_CBC.equals(algorithm)) + { + cipher = new CBCBlockCipher(new DESedeEngine()); + } + else if (OIWObjectIdentifiers.desCBC.equals(algorithm)) + { + cipher = new CBCBlockCipher(new DESEngine()); + } + else if (PKCSObjectIdentifiers.RC2_CBC.equals(algorithm)) + { + cipher = new CBCBlockCipher(new RC2Engine()); + } + else + { + throw new CMSException("cannot recognise cipher: " + algorithm); + } + + return new PaddedBufferedBlockCipher(cipher, new PKCS7Padding()); + } + + static Wrapper createRFC3211Wrapper(ASN1ObjectIdentifier algorithm) + throws CMSException + { + if (NISTObjectIdentifiers.id_aes128_CBC.equals(algorithm) + || NISTObjectIdentifiers.id_aes192_CBC.equals(algorithm) + || NISTObjectIdentifiers.id_aes256_CBC.equals(algorithm)) + { + return new RFC3211WrapEngine(new AESEngine()); + } + else if (PKCSObjectIdentifiers.des_EDE3_CBC.equals(algorithm)) + { + return new RFC3211WrapEngine(new DESedeEngine()); + } + else if (OIWObjectIdentifiers.desCBC.equals(algorithm)) + { + return new RFC3211WrapEngine(new DESEngine()); + } + else if (PKCSObjectIdentifiers.RC2_CBC.equals(algorithm)) + { + return new RFC3211WrapEngine(new RC2Engine()); + } + else + { + throw new CMSException("cannot recognise wrapper: " + algorithm); + } + } + + static Object createContentCipher(boolean forEncryption, CipherParameters encKey, AlgorithmIdentifier encryptionAlgID) + throws CMSException + { + ASN1ObjectIdentifier encAlg = encryptionAlgID.getAlgorithm(); + + if (encAlg.equals(PKCSObjectIdentifiers.rc4)) + { + StreamCipher cipher = new RC4Engine(); + + cipher.init(forEncryption, encKey); + + return cipher; + } + else + { + BufferedBlockCipher cipher = createCipher(encryptionAlgID.getAlgorithm()); + ASN1Primitive sParams = encryptionAlgID.getParameters().toASN1Primitive(); + + if (sParams != null && !(sParams instanceof ASN1Null)) + { + if (encAlg.equals(CMSAlgorithm.DES_EDE3_CBC) + || encAlg.equals(CMSAlgorithm.IDEA_CBC) + || encAlg.equals(CMSAlgorithm.AES128_CBC) + || encAlg.equals(CMSAlgorithm.AES192_CBC) + || encAlg.equals(CMSAlgorithm.AES256_CBC) + || encAlg.equals(CMSAlgorithm.CAMELLIA128_CBC) + || encAlg.equals(CMSAlgorithm.CAMELLIA192_CBC) + || encAlg.equals(CMSAlgorithm.CAMELLIA256_CBC) + || encAlg.equals(CMSAlgorithm.SEED_CBC) + || encAlg.equals(OIWObjectIdentifiers.desCBC)) + { + cipher.init(forEncryption, new ParametersWithIV(encKey, + ASN1OctetString.getInstance(sParams).getOctets())); + } + else if (encAlg.equals(CMSAlgorithm.CAST5_CBC)) + { + CAST5CBCParameters cbcParams = CAST5CBCParameters.getInstance(sParams); + + cipher.init(forEncryption, new ParametersWithIV(encKey, cbcParams.getIV())); + } + else if (encAlg.equals(CMSAlgorithm.RC2_CBC)) + { + RC2CBCParameter cbcParams = RC2CBCParameter.getInstance(sParams); + + cipher.init(forEncryption, new ParametersWithIV(new RC2Parameters(((KeyParameter)encKey).getKey(), rc2Ekb[cbcParams.getRC2ParameterVersion().intValue()]), cbcParams.getIV())); + } + else + { + throw new CMSException("cannot match parameters"); + } + } + else + { + if (encAlg.equals(CMSAlgorithm.DES_EDE3_CBC) + || encAlg.equals(CMSAlgorithm.IDEA_CBC) + || encAlg.equals(CMSAlgorithm.CAST5_CBC)) + { + cipher.init(forEncryption, new ParametersWithIV(encKey, new byte[8])); + } + else + { + cipher.init(forEncryption, encKey); + } + } + + return cipher; + } + } + + AlgorithmIdentifier generateAlgorithmIdentifier(ASN1ObjectIdentifier encryptionOID, CipherParameters encKey, SecureRandom random) + throws CMSException + { + if (encryptionOID.equals(CMSAlgorithm.AES128_CBC) + || encryptionOID.equals(CMSAlgorithm.AES192_CBC) + || encryptionOID.equals(CMSAlgorithm.AES256_CBC) + || encryptionOID.equals(CMSAlgorithm.CAMELLIA128_CBC) + || encryptionOID.equals(CMSAlgorithm.CAMELLIA192_CBC) + || encryptionOID.equals(CMSAlgorithm.CAMELLIA256_CBC) + || encryptionOID.equals(CMSAlgorithm.SEED_CBC)) + { + byte[] iv = new byte[16]; + + random.nextBytes(iv); + + return new AlgorithmIdentifier(encryptionOID, new DEROctetString(iv)); + } + else if (encryptionOID.equals(CMSAlgorithm.DES_EDE3_CBC) + || encryptionOID.equals(CMSAlgorithm.IDEA_CBC) + || encryptionOID.equals(OIWObjectIdentifiers.desCBC)) + { + byte[] iv = new byte[8]; + + random.nextBytes(iv); + + return new AlgorithmIdentifier(encryptionOID, new DEROctetString(iv)); + } + else if (encryptionOID.equals(CMSAlgorithm.CAST5_CBC)) + { + byte[] iv = new byte[8]; + + random.nextBytes(iv); + + CAST5CBCParameters cbcParams = new CAST5CBCParameters(iv, ((KeyParameter)encKey).getKey().length * 8); + + return new AlgorithmIdentifier(encryptionOID, cbcParams); + } + else if (encryptionOID.equals(PKCSObjectIdentifiers.rc4)) + { + return new AlgorithmIdentifier(encryptionOID, DERNull.INSTANCE); + } + else + { + throw new CMSException("unable to match algorithm"); + } + } + + CipherKeyGenerator createKeyGenerator(ASN1ObjectIdentifier algorithm, SecureRandom random) + throws CMSException + { + if (NISTObjectIdentifiers.id_aes128_CBC.equals(algorithm)) + { + return createCipherKeyGenerator(random, 128); + } + else if (NISTObjectIdentifiers.id_aes192_CBC.equals(algorithm)) + { + return createCipherKeyGenerator(random, 192); + } + else if (NISTObjectIdentifiers.id_aes256_CBC.equals(algorithm)) + { + return createCipherKeyGenerator(random, 256); + } + else if (PKCSObjectIdentifiers.des_EDE3_CBC.equals(algorithm)) + { + DESedeKeyGenerator keyGen = new DESedeKeyGenerator(); + + keyGen.init(new KeyGenerationParameters(random, 192)); + + return keyGen; + } + else if (NTTObjectIdentifiers.id_camellia128_cbc.equals(algorithm)) + { + return createCipherKeyGenerator(random, 128); + } + else if (NTTObjectIdentifiers.id_camellia192_cbc.equals(algorithm)) + { + return createCipherKeyGenerator(random, 192); + } + else if (NTTObjectIdentifiers.id_camellia256_cbc.equals(algorithm)) + { + return createCipherKeyGenerator(random, 256); + } + else if (KISAObjectIdentifiers.id_seedCBC.equals(algorithm)) + { + return createCipherKeyGenerator(random, 128); + } + else if (CMSAlgorithm.CAST5_CBC.equals(algorithm)) + { + return createCipherKeyGenerator(random, 128); + } + else if (OIWObjectIdentifiers.desCBC.equals(algorithm)) + { + DESKeyGenerator keyGen = new DESKeyGenerator(); + + keyGen.init(new KeyGenerationParameters(random, 64)); + + return keyGen; + } + else if (PKCSObjectIdentifiers.rc4.equals(algorithm)) + { + return createCipherKeyGenerator(random, 128); + } +// else if (PKCSObjectIdentifiers.RC2_CBC.equals(algorithm)) +// { +// cipher = new CBCBlockCipher(new RC2Engine()); +// } + else + { + throw new CMSException("cannot recognise cipher: " + algorithm); + } + + } + + private CipherKeyGenerator createCipherKeyGenerator(SecureRandom random, int keySize) + { + CipherKeyGenerator keyGen = new CipherKeyGenerator(); + + keyGen.init(new KeyGenerationParameters(random, keySize)); + + return keyGen; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/CMSUtils.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/CMSUtils.java new file mode 100644 index 000000000..a0a0812cb --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/CMSUtils.java @@ -0,0 +1,99 @@ +package org.spongycastle.cms.jcajce; + +import java.io.IOException; +import java.security.AlgorithmParameters; +import java.security.Provider; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.cms.IssuerAndSerialNumber; +import org.spongycastle.asn1.x509.Certificate; +import org.spongycastle.asn1.x509.Extension; +import org.spongycastle.asn1.x509.TBSCertificateStructure; +import org.spongycastle.cms.CMSException; +import org.spongycastle.jcajce.JcaJceUtils; + +class CMSUtils +{ + static TBSCertificateStructure getTBSCertificateStructure( + X509Certificate cert) + throws CertificateEncodingException + { + return TBSCertificateStructure.getInstance(cert.getTBSCertificate()); + } + + static IssuerAndSerialNumber getIssuerAndSerialNumber(X509Certificate cert) + throws CertificateEncodingException + { + Certificate certStruct = Certificate.getInstance(cert.getEncoded()); + + return new IssuerAndSerialNumber(certStruct.getIssuer(), cert.getSerialNumber()); + } + + + static byte[] getSubjectKeyId(X509Certificate cert) + { + byte[] ext = cert.getExtensionValue(Extension.subjectKeyIdentifier.getId()); + + if (ext != null) + { + return ASN1OctetString.getInstance(ASN1OctetString.getInstance(ext).getOctets()).getOctets(); + } + else + { + return null; + } + } + + static EnvelopedDataHelper createContentHelper(Provider provider) + { + if (provider != null) + { + return new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider)); + } + else + { + return new EnvelopedDataHelper(new DefaultJcaJceExtHelper()); + } + } + + static EnvelopedDataHelper createContentHelper(String providerName) + { + if (providerName != null) + { + return new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName)); + } + else + { + return new EnvelopedDataHelper(new DefaultJcaJceExtHelper()); + } + } + + static ASN1Encodable extractParameters(AlgorithmParameters params) + throws CMSException + { + try + { + return JcaJceUtils.extractParameters(params); + } + catch (IOException e) + { + throw new CMSException("cannot extract parameters: " + e.getMessage(), e); + } + } + + static void loadParameters(AlgorithmParameters params, ASN1Encodable sParams) + throws CMSException + { + try + { + JcaJceUtils.loadParameters(params, sParams); + } + catch (IOException e) + { + throw new CMSException("error encoding algorithm parameters.", e); + } + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/DefaultJcaJceExtHelper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/DefaultJcaJceExtHelper.java new file mode 100644 index 000000000..c0dfc3e80 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/DefaultJcaJceExtHelper.java @@ -0,0 +1,26 @@ +package org.spongycastle.cms.jcajce; + +import java.security.PrivateKey; + +import javax.crypto.SecretKey; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.jcajce.DefaultJcaJceHelper; +import org.spongycastle.operator.SymmetricKeyUnwrapper; +import org.spongycastle.operator.jcajce.JceAsymmetricKeyUnwrapper; +import org.spongycastle.operator.jcajce.JceSymmetricKeyUnwrapper; + +class DefaultJcaJceExtHelper + extends DefaultJcaJceHelper + implements JcaJceExtHelper +{ + public JceAsymmetricKeyUnwrapper createAsymmetricUnwrapper(AlgorithmIdentifier keyEncryptionAlgorithm, PrivateKey keyEncryptionKey) + { + return new JceAsymmetricKeyUnwrapper(keyEncryptionAlgorithm, keyEncryptionKey); + } + + public SymmetricKeyUnwrapper createSymmetricUnwrapper(AlgorithmIdentifier keyEncryptionAlgorithm, SecretKey keyEncryptionKey) + { + return new JceSymmetricKeyUnwrapper(keyEncryptionAlgorithm, keyEncryptionKey); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/EnvelopedDataHelper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/EnvelopedDataHelper.java new file mode 100644 index 000000000..93de500e7 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/EnvelopedDataHelper.java @@ -0,0 +1,668 @@ +package org.spongycastle.cms.jcajce; + +import java.security.AlgorithmParameterGenerator; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidParameterSpecException; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.Cipher; +import javax.crypto.KeyAgreement; +import javax.crypto.KeyGenerator; +import javax.crypto.Mac; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.RC2ParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1Null; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.DERNull; +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.pkcs.RC2CBCParameter; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cms.CMSAlgorithm; +import org.spongycastle.cms.CMSEnvelopedDataGenerator; +import org.spongycastle.cms.CMSException; +import org.spongycastle.operator.DefaultSecretKeySizeProvider; +import org.spongycastle.operator.GenericKey; +import org.spongycastle.operator.SecretKeySizeProvider; +import org.spongycastle.operator.SymmetricKeyUnwrapper; +import org.spongycastle.operator.jcajce.JceAsymmetricKeyUnwrapper; + +public class EnvelopedDataHelper +{ + protected static final SecretKeySizeProvider KEY_SIZE_PROVIDER = DefaultSecretKeySizeProvider.INSTANCE; + + protected static final Map BASE_CIPHER_NAMES = new HashMap(); + protected static final Map CIPHER_ALG_NAMES = new HashMap(); + protected static final Map MAC_ALG_NAMES = new HashMap(); + + static + { + BASE_CIPHER_NAMES.put(CMSAlgorithm.DES_CBC, "DES"); + BASE_CIPHER_NAMES.put(CMSAlgorithm.DES_EDE3_CBC, "DESEDE"); + BASE_CIPHER_NAMES.put(CMSAlgorithm.AES128_CBC, "AES"); + BASE_CIPHER_NAMES.put(CMSAlgorithm.AES192_CBC, "AES"); + BASE_CIPHER_NAMES.put(CMSAlgorithm.AES256_CBC, "AES"); + BASE_CIPHER_NAMES.put(CMSAlgorithm.RC2_CBC, "RC2"); + BASE_CIPHER_NAMES.put(CMSAlgorithm.CAST5_CBC, "CAST5"); + BASE_CIPHER_NAMES.put(CMSAlgorithm.CAMELLIA128_CBC, "Camellia"); + BASE_CIPHER_NAMES.put(CMSAlgorithm.CAMELLIA192_CBC, "Camellia"); + BASE_CIPHER_NAMES.put(CMSAlgorithm.CAMELLIA256_CBC, "Camellia"); + BASE_CIPHER_NAMES.put(CMSAlgorithm.SEED_CBC, "SEED"); + BASE_CIPHER_NAMES.put(PKCSObjectIdentifiers.rc4, "RC4"); + + CIPHER_ALG_NAMES.put(CMSAlgorithm.DES_CBC, "DES/CBC/PKCS5Padding"); + CIPHER_ALG_NAMES.put(CMSAlgorithm.RC2_CBC, "RC2/CBC/PKCS5Padding"); + CIPHER_ALG_NAMES.put(CMSAlgorithm.DES_EDE3_CBC, "DESEDE/CBC/PKCS5Padding"); + CIPHER_ALG_NAMES.put(CMSAlgorithm.AES128_CBC, "AES/CBC/PKCS5Padding"); + CIPHER_ALG_NAMES.put(CMSAlgorithm.AES192_CBC, "AES/CBC/PKCS5Padding"); + CIPHER_ALG_NAMES.put(CMSAlgorithm.AES256_CBC, "AES/CBC/PKCS5Padding"); + CIPHER_ALG_NAMES.put(PKCSObjectIdentifiers.rsaEncryption, "RSA/ECB/PKCS1Padding"); + CIPHER_ALG_NAMES.put(CMSAlgorithm.CAST5_CBC, "CAST5/CBC/PKCS5Padding"); + CIPHER_ALG_NAMES.put(CMSAlgorithm.CAMELLIA128_CBC, "Camellia/CBC/PKCS5Padding"); + CIPHER_ALG_NAMES.put(CMSAlgorithm.CAMELLIA192_CBC, "Camellia/CBC/PKCS5Padding"); + CIPHER_ALG_NAMES.put(CMSAlgorithm.CAMELLIA256_CBC, "Camellia/CBC/PKCS5Padding"); + CIPHER_ALG_NAMES.put(CMSAlgorithm.SEED_CBC, "SEED/CBC/PKCS5Padding"); + CIPHER_ALG_NAMES.put(PKCSObjectIdentifiers.rc4, "RC4"); + + MAC_ALG_NAMES.put(CMSAlgorithm.DES_EDE3_CBC, "DESEDEMac"); + MAC_ALG_NAMES.put(CMSAlgorithm.AES128_CBC, "AESMac"); + MAC_ALG_NAMES.put(CMSAlgorithm.AES192_CBC, "AESMac"); + MAC_ALG_NAMES.put(CMSAlgorithm.AES256_CBC, "AESMac"); + MAC_ALG_NAMES.put(CMSAlgorithm.RC2_CBC, "RC2Mac"); + } + + private static final short[] rc2Table = { + 0xbd, 0x56, 0xea, 0xf2, 0xa2, 0xf1, 0xac, 0x2a, 0xb0, 0x93, 0xd1, 0x9c, 0x1b, 0x33, 0xfd, 0xd0, + 0x30, 0x04, 0xb6, 0xdc, 0x7d, 0xdf, 0x32, 0x4b, 0xf7, 0xcb, 0x45, 0x9b, 0x31, 0xbb, 0x21, 0x5a, + 0x41, 0x9f, 0xe1, 0xd9, 0x4a, 0x4d, 0x9e, 0xda, 0xa0, 0x68, 0x2c, 0xc3, 0x27, 0x5f, 0x80, 0x36, + 0x3e, 0xee, 0xfb, 0x95, 0x1a, 0xfe, 0xce, 0xa8, 0x34, 0xa9, 0x13, 0xf0, 0xa6, 0x3f, 0xd8, 0x0c, + 0x78, 0x24, 0xaf, 0x23, 0x52, 0xc1, 0x67, 0x17, 0xf5, 0x66, 0x90, 0xe7, 0xe8, 0x07, 0xb8, 0x60, + 0x48, 0xe6, 0x1e, 0x53, 0xf3, 0x92, 0xa4, 0x72, 0x8c, 0x08, 0x15, 0x6e, 0x86, 0x00, 0x84, 0xfa, + 0xf4, 0x7f, 0x8a, 0x42, 0x19, 0xf6, 0xdb, 0xcd, 0x14, 0x8d, 0x50, 0x12, 0xba, 0x3c, 0x06, 0x4e, + 0xec, 0xb3, 0x35, 0x11, 0xa1, 0x88, 0x8e, 0x2b, 0x94, 0x99, 0xb7, 0x71, 0x74, 0xd3, 0xe4, 0xbf, + 0x3a, 0xde, 0x96, 0x0e, 0xbc, 0x0a, 0xed, 0x77, 0xfc, 0x37, 0x6b, 0x03, 0x79, 0x89, 0x62, 0xc6, + 0xd7, 0xc0, 0xd2, 0x7c, 0x6a, 0x8b, 0x22, 0xa3, 0x5b, 0x05, 0x5d, 0x02, 0x75, 0xd5, 0x61, 0xe3, + 0x18, 0x8f, 0x55, 0x51, 0xad, 0x1f, 0x0b, 0x5e, 0x85, 0xe5, 0xc2, 0x57, 0x63, 0xca, 0x3d, 0x6c, + 0xb4, 0xc5, 0xcc, 0x70, 0xb2, 0x91, 0x59, 0x0d, 0x47, 0x20, 0xc8, 0x4f, 0x58, 0xe0, 0x01, 0xe2, + 0x16, 0x38, 0xc4, 0x6f, 0x3b, 0x0f, 0x65, 0x46, 0xbe, 0x7e, 0x2d, 0x7b, 0x82, 0xf9, 0x40, 0xb5, + 0x1d, 0x73, 0xf8, 0xeb, 0x26, 0xc7, 0x87, 0x97, 0x25, 0x54, 0xb1, 0x28, 0xaa, 0x98, 0x9d, 0xa5, + 0x64, 0x6d, 0x7a, 0xd4, 0x10, 0x81, 0x44, 0xef, 0x49, 0xd6, 0xae, 0x2e, 0xdd, 0x76, 0x5c, 0x2f, + 0xa7, 0x1c, 0xc9, 0x09, 0x69, 0x9a, 0x83, 0xcf, 0x29, 0x39, 0xb9, 0xe9, 0x4c, 0xff, 0x43, 0xab + }; + + private static final short[] rc2Ekb = { + 0x5d, 0xbe, 0x9b, 0x8b, 0x11, 0x99, 0x6e, 0x4d, 0x59, 0xf3, 0x85, 0xa6, 0x3f, 0xb7, 0x83, 0xc5, + 0xe4, 0x73, 0x6b, 0x3a, 0x68, 0x5a, 0xc0, 0x47, 0xa0, 0x64, 0x34, 0x0c, 0xf1, 0xd0, 0x52, 0xa5, + 0xb9, 0x1e, 0x96, 0x43, 0x41, 0xd8, 0xd4, 0x2c, 0xdb, 0xf8, 0x07, 0x77, 0x2a, 0xca, 0xeb, 0xef, + 0x10, 0x1c, 0x16, 0x0d, 0x38, 0x72, 0x2f, 0x89, 0xc1, 0xf9, 0x80, 0xc4, 0x6d, 0xae, 0x30, 0x3d, + 0xce, 0x20, 0x63, 0xfe, 0xe6, 0x1a, 0xc7, 0xb8, 0x50, 0xe8, 0x24, 0x17, 0xfc, 0x25, 0x6f, 0xbb, + 0x6a, 0xa3, 0x44, 0x53, 0xd9, 0xa2, 0x01, 0xab, 0xbc, 0xb6, 0x1f, 0x98, 0xee, 0x9a, 0xa7, 0x2d, + 0x4f, 0x9e, 0x8e, 0xac, 0xe0, 0xc6, 0x49, 0x46, 0x29, 0xf4, 0x94, 0x8a, 0xaf, 0xe1, 0x5b, 0xc3, + 0xb3, 0x7b, 0x57, 0xd1, 0x7c, 0x9c, 0xed, 0x87, 0x40, 0x8c, 0xe2, 0xcb, 0x93, 0x14, 0xc9, 0x61, + 0x2e, 0xe5, 0xcc, 0xf6, 0x5e, 0xa8, 0x5c, 0xd6, 0x75, 0x8d, 0x62, 0x95, 0x58, 0x69, 0x76, 0xa1, + 0x4a, 0xb5, 0x55, 0x09, 0x78, 0x33, 0x82, 0xd7, 0xdd, 0x79, 0xf5, 0x1b, 0x0b, 0xde, 0x26, 0x21, + 0x28, 0x74, 0x04, 0x97, 0x56, 0xdf, 0x3c, 0xf0, 0x37, 0x39, 0xdc, 0xff, 0x06, 0xa4, 0xea, 0x42, + 0x08, 0xda, 0xb4, 0x71, 0xb0, 0xcf, 0x12, 0x7a, 0x4e, 0xfa, 0x6c, 0x1d, 0x84, 0x00, 0xc8, 0x7f, + 0x91, 0x45, 0xaa, 0x2b, 0xc2, 0xb1, 0x8f, 0xd5, 0xba, 0xf2, 0xad, 0x19, 0xb2, 0x67, 0x36, 0xf7, + 0x0f, 0x0a, 0x92, 0x7d, 0xe3, 0x9d, 0xe9, 0x90, 0x3e, 0x23, 0x27, 0x66, 0x13, 0xec, 0x81, 0x15, + 0xbd, 0x22, 0xbf, 0x9f, 0x7e, 0xa9, 0x51, 0x4b, 0x4c, 0xfb, 0x02, 0xd3, 0x70, 0x86, 0x31, 0xe7, + 0x3b, 0x05, 0x03, 0x54, 0x60, 0x48, 0x65, 0x18, 0xd2, 0xcd, 0x5f, 0x32, 0x88, 0x0e, 0x35, 0xfd + }; + + private JcaJceExtHelper helper; + + EnvelopedDataHelper(JcaJceExtHelper helper) + { + this.helper = helper; + } + + String getBaseCipherName(ASN1ObjectIdentifier algorithm) + { + String name = (String)BASE_CIPHER_NAMES.get(algorithm); + + if (name == null) + { + return algorithm.getId(); + } + + return name; + } + + Key getJceKey(GenericKey key) + { + if (key.getRepresentation() instanceof Key) + { + return (Key)key.getRepresentation(); + } + + if (key.getRepresentation() instanceof byte[]) + { + return new SecretKeySpec((byte[])key.getRepresentation(), "ENC"); + } + + throw new IllegalArgumentException("unknown generic key type"); + } + + public Key getJceKey(ASN1ObjectIdentifier algorithm, GenericKey key) + { + if (key.getRepresentation() instanceof Key) + { + return (Key)key.getRepresentation(); + } + + if (key.getRepresentation() instanceof byte[]) + { + return new SecretKeySpec((byte[])key.getRepresentation(), getBaseCipherName(algorithm)); + } + + throw new IllegalArgumentException("unknown generic key type"); + } + + public void keySizeCheck(AlgorithmIdentifier keyAlgorithm, Key key) + throws CMSException + { + int expectedKeySize = EnvelopedDataHelper.KEY_SIZE_PROVIDER.getKeySize(keyAlgorithm); + if (expectedKeySize > 0) + { + byte[] keyEnc = null; + + try + { + keyEnc = key.getEncoded(); + } + catch (Exception e) + { + // ignore - we're using a HSM... + } + + if (keyEnc != null) + { + if (keyEnc.length * 8 != expectedKeySize) + { + throw new CMSException("Expected key size for algorithm OID not found in recipient."); + } + } + } + } + + Cipher createCipher(ASN1ObjectIdentifier algorithm) + throws CMSException + { + try + { + String cipherName = (String)CIPHER_ALG_NAMES.get(algorithm); + + if (cipherName != null) + { + try + { + // this is reversed as the Sun policy files now allow unlimited strength RSA + return helper.createCipher(cipherName); + } + catch (NoSuchAlgorithmException e) + { + // Ignore + } + } + return helper.createCipher(algorithm.getId()); + } + catch (GeneralSecurityException e) + { + throw new CMSException("cannot create cipher: " + e.getMessage(), e); + } + } + + Mac createMac(ASN1ObjectIdentifier algorithm) + throws CMSException + { + try + { + String macName = (String)MAC_ALG_NAMES.get(algorithm); + + if (macName != null) + { + try + { + // this is reversed as the Sun policy files now allow unlimited strength RSA + return helper.createMac(macName); + } + catch (NoSuchAlgorithmException e) + { + // Ignore + } + } + return helper.createMac(algorithm.getId()); + } + catch (GeneralSecurityException e) + { + throw new CMSException("cannot create mac: " + e.getMessage(), e); + } + } + + Cipher createRFC3211Wrapper(ASN1ObjectIdentifier algorithm) + throws CMSException + { + String cipherName = (String)BASE_CIPHER_NAMES.get(algorithm); + + if (cipherName == null) + { + throw new CMSException("no name for " + algorithm); + } + + cipherName += "RFC3211Wrap"; + + try + { + return helper.createCipher(cipherName); + } + catch (GeneralSecurityException e) + { + throw new CMSException("cannot create cipher: " + e.getMessage(), e); + } + } + + KeyAgreement createKeyAgreement(ASN1ObjectIdentifier algorithm) + throws CMSException + { + try + { + String agreementName = (String)BASE_CIPHER_NAMES.get(algorithm); + + if (agreementName != null) + { + try + { + // this is reversed as the Sun policy files now allow unlimited strength RSA + return helper.createKeyAgreement(agreementName); + } + catch (NoSuchAlgorithmException e) + { + // Ignore + } + } + return helper.createKeyAgreement(algorithm.getId()); + } + catch (GeneralSecurityException e) + { + throw new CMSException("cannot create key pair generator: " + e.getMessage(), e); + } + } + + AlgorithmParameterGenerator createAlgorithmParameterGenerator(ASN1ObjectIdentifier algorithm) + throws GeneralSecurityException + { + String algorithmName = (String)BASE_CIPHER_NAMES.get(algorithm); + + if (algorithmName != null) + { + try + { + // this is reversed as the Sun policy files now allow unlimited strength RSA + return helper.createAlgorithmParameterGenerator(algorithmName); + } + catch (NoSuchAlgorithmException e) + { + // Ignore + } + } + return helper.createAlgorithmParameterGenerator(algorithm.getId()); + } + + public Cipher createContentCipher(final Key sKey, final AlgorithmIdentifier encryptionAlgID) + throws CMSException + { + return (Cipher)execute(new JCECallback() + { + public Object doInJCE() + throws CMSException, InvalidAlgorithmParameterException, + InvalidKeyException, InvalidParameterSpecException, NoSuchAlgorithmException, + NoSuchPaddingException, NoSuchProviderException + { + Cipher cipher = createCipher(encryptionAlgID.getAlgorithm()); + ASN1Encodable sParams = encryptionAlgID.getParameters(); + String encAlg = encryptionAlgID.getAlgorithm().getId(); + + if (sParams != null && !(sParams instanceof ASN1Null)) + { + try + { + AlgorithmParameters params = createAlgorithmParameters(encryptionAlgID.getAlgorithm()); + + CMSUtils.loadParameters(params, sParams); + + cipher.init(Cipher.DECRYPT_MODE, sKey, params); + } + catch (NoSuchAlgorithmException e) + { + if (encAlg.equals(CMSAlgorithm.DES_CBC.getId()) + || encAlg.equals(CMSEnvelopedDataGenerator.DES_EDE3_CBC) + || encAlg.equals(CMSEnvelopedDataGenerator.IDEA_CBC) + || encAlg.equals(CMSEnvelopedDataGenerator.AES128_CBC) + || encAlg.equals(CMSEnvelopedDataGenerator.AES192_CBC) + || encAlg.equals(CMSEnvelopedDataGenerator.AES256_CBC)) + { + cipher.init(Cipher.DECRYPT_MODE, sKey, new IvParameterSpec( + ASN1OctetString.getInstance(sParams).getOctets())); + } + else + { + throw e; + } + } + } + else + { + if (encAlg.equals(CMSAlgorithm.DES_CBC.getId()) + || encAlg.equals(CMSEnvelopedDataGenerator.DES_EDE3_CBC) + || encAlg.equals(CMSEnvelopedDataGenerator.IDEA_CBC) + || encAlg.equals(CMSEnvelopedDataGenerator.CAST5_CBC)) + { + cipher.init(Cipher.DECRYPT_MODE, sKey, new IvParameterSpec(new byte[8])); + } + else + { + cipher.init(Cipher.DECRYPT_MODE, sKey); + } + } + + return cipher; + } + }); + } + + Mac createContentMac(final Key sKey, final AlgorithmIdentifier macAlgId) + throws CMSException + { + return (Mac)execute(new JCECallback() + { + public Object doInJCE() + throws CMSException, InvalidAlgorithmParameterException, + InvalidKeyException, InvalidParameterSpecException, NoSuchAlgorithmException, + NoSuchPaddingException, NoSuchProviderException + { + Mac mac = createMac(macAlgId.getAlgorithm()); + ASN1Encodable sParams = macAlgId.getParameters(); + String macAlg = macAlgId.getAlgorithm().getId(); + + if (sParams != null && !(sParams instanceof ASN1Null)) + { + try + { + AlgorithmParameters params = createAlgorithmParameters(macAlgId.getAlgorithm()); + + CMSUtils.loadParameters(params, sParams); + + mac.init(sKey, params.getParameterSpec(IvParameterSpec.class)); + } + catch (NoSuchAlgorithmException e) + { + throw e; + } + } + else + { + mac.init(sKey); + } + + return mac; + } + }); + } + + AlgorithmParameters createAlgorithmParameters(ASN1ObjectIdentifier algorithm) + throws NoSuchAlgorithmException, NoSuchProviderException + { + String algorithmName = (String)BASE_CIPHER_NAMES.get(algorithm); + + if (algorithmName != null) + { + try + { + // this is reversed as the Sun policy files now allow unlimited strength RSA + return helper.createAlgorithmParameters(algorithmName); + } + catch (NoSuchAlgorithmException e) + { + // Ignore + } + } + return helper.createAlgorithmParameters(algorithm.getId()); + } + + + KeyPairGenerator createKeyPairGenerator(ASN1ObjectIdentifier algorithm) + throws CMSException + { + try + { + String cipherName = (String)BASE_CIPHER_NAMES.get(algorithm); + + if (cipherName != null) + { + try + { + // this is reversed as the Sun policy files now allow unlimited strength RSA + return helper.createKeyPairGenerator(cipherName); + } + catch (NoSuchAlgorithmException e) + { + // Ignore + } + } + return helper.createKeyPairGenerator(algorithm.getId()); + } + catch (GeneralSecurityException e) + { + throw new CMSException("cannot create key pair generator: " + e.getMessage(), e); + } + } + + public KeyGenerator createKeyGenerator(ASN1ObjectIdentifier algorithm) + throws CMSException + { + try + { + String cipherName = (String)BASE_CIPHER_NAMES.get(algorithm); + + if (cipherName != null) + { + try + { + // this is reversed as the Sun policy files now allow unlimited strength RSA + return helper.createKeyGenerator(cipherName); + } + catch (NoSuchAlgorithmException e) + { + // Ignore + } + } + return helper.createKeyGenerator(algorithm.getId()); + } + catch (GeneralSecurityException e) + { + throw new CMSException("cannot create key generator: " + e.getMessage(), e); + } + } + + AlgorithmParameters generateParameters(ASN1ObjectIdentifier encryptionOID, SecretKey encKey, SecureRandom rand) + throws CMSException + { + try + { + AlgorithmParameterGenerator pGen = createAlgorithmParameterGenerator(encryptionOID); + + if (encryptionOID.equals(CMSAlgorithm.RC2_CBC)) + { + byte[] iv = new byte[8]; + + rand.nextBytes(iv); + + try + { + pGen.init(new RC2ParameterSpec(encKey.getEncoded().length * 8, iv), rand); + } + catch (InvalidAlgorithmParameterException e) + { + throw new CMSException("parameters generation error: " + e, e); + } + } + + return pGen.generateParameters(); + } + catch (NoSuchAlgorithmException e) + { + return null; + } + catch (GeneralSecurityException e) + { + throw new CMSException("exception creating algorithm parameter generator: " + e, e); + } + } + + AlgorithmIdentifier getAlgorithmIdentifier(ASN1ObjectIdentifier encryptionOID, AlgorithmParameters params) + throws CMSException + { + ASN1Encodable asn1Params; + if (params != null) + { + asn1Params = CMSUtils.extractParameters(params); + } + else + { + asn1Params = DERNull.INSTANCE; + } + + return new AlgorithmIdentifier( + encryptionOID, + asn1Params); + } + + static Object execute(JCECallback callback) throws CMSException + { + try + { + return callback.doInJCE(); + } + catch (NoSuchAlgorithmException e) + { + throw new CMSException("can't find algorithm.", e); + } + catch (InvalidKeyException e) + { + throw new CMSException("key invalid in message.", e); + } + catch (NoSuchProviderException e) + { + throw new CMSException("can't find provider.", e); + } + catch (NoSuchPaddingException e) + { + throw new CMSException("required padding not supported.", e); + } + catch (InvalidAlgorithmParameterException e) + { + throw new CMSException("algorithm parameters invalid.", e); + } + catch (InvalidParameterSpecException e) + { + throw new CMSException("MAC algorithm parameter spec invalid.", e); + } + } + + public KeyFactory createKeyFactory(ASN1ObjectIdentifier algorithm) + throws CMSException + { + try + { + String cipherName = (String)BASE_CIPHER_NAMES.get(algorithm); + + if (cipherName != null) + { + try + { + // this is reversed as the Sun policy files now allow unlimited strength RSA + return helper.createKeyFactory(cipherName); + } + catch (NoSuchAlgorithmException e) + { + // Ignore + } + } + return helper.createKeyFactory(algorithm.getId()); + } + catch (GeneralSecurityException e) + { + throw new CMSException("cannot create key factory: " + e.getMessage(), e); + } + } + + public JceAsymmetricKeyUnwrapper createAsymmetricUnwrapper(AlgorithmIdentifier keyEncryptionAlgorithm, PrivateKey keyEncryptionKey) + { + return helper.createAsymmetricUnwrapper(keyEncryptionAlgorithm, keyEncryptionKey); + } + + public SymmetricKeyUnwrapper createSymmetricUnwrapper(AlgorithmIdentifier keyEncryptionAlgorithm, SecretKey keyEncryptionKey) + { + return helper.createSymmetricUnwrapper(keyEncryptionAlgorithm, keyEncryptionKey); + } + + public AlgorithmIdentifier getAlgorithmIdentifier(ASN1ObjectIdentifier macOID, AlgorithmParameterSpec paramSpec) + { + if (paramSpec instanceof IvParameterSpec) + { + return new AlgorithmIdentifier(macOID, new DEROctetString(((IvParameterSpec)paramSpec).getIV())); + } + + if (paramSpec instanceof RC2ParameterSpec) + { + RC2ParameterSpec rc2Spec = (RC2ParameterSpec)paramSpec; + + int effKeyBits = ((RC2ParameterSpec)paramSpec).getEffectiveKeyBits(); + + if (effKeyBits != -1) + { + int parameterVersion; + + if (effKeyBits < 256) + { + parameterVersion = rc2Table[effKeyBits]; + } + else + { + parameterVersion = effKeyBits; + } + + return new AlgorithmIdentifier(macOID, new RC2CBCParameter(parameterVersion, rc2Spec.getIV())); + } + + return new AlgorithmIdentifier(macOID, new RC2CBCParameter(rc2Spec.getIV())); + } + + throw new IllegalStateException("unknown parameter spec: " + paramSpec); + } + + static interface JCECallback + { + Object doInJCE() + throws CMSException, InvalidAlgorithmParameterException, InvalidKeyException, InvalidParameterSpecException, + NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcaJceExtHelper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcaJceExtHelper.java new file mode 100644 index 000000000..e4dd991c0 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcaJceExtHelper.java @@ -0,0 +1,18 @@ +package org.spongycastle.cms.jcajce; + +import java.security.PrivateKey; + +import javax.crypto.SecretKey; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.jcajce.JcaJceHelper; +import org.spongycastle.operator.SymmetricKeyUnwrapper; +import org.spongycastle.operator.jcajce.JceAsymmetricKeyUnwrapper; + +public interface JcaJceExtHelper + extends JcaJceHelper +{ + JceAsymmetricKeyUnwrapper createAsymmetricUnwrapper(AlgorithmIdentifier keyEncryptionAlgorithm, PrivateKey keyEncryptionKey); + + SymmetricKeyUnwrapper createSymmetricUnwrapper(AlgorithmIdentifier keyEncryptionAlgorithm, SecretKey keyEncryptionKey); +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcaSelectorConverter.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcaSelectorConverter.java new file mode 100644 index 000000000..6dc5c9a87 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcaSelectorConverter.java @@ -0,0 +1,55 @@ +package org.spongycastle.cms.jcajce; + +import java.io.IOException; +import java.security.cert.X509CertSelector; + +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.cms.KeyTransRecipientId; +import org.spongycastle.cms.SignerId; + +public class JcaSelectorConverter +{ + public JcaSelectorConverter() + { + + } + + public SignerId getSignerId(X509CertSelector certSelector) + { + try + { + if (certSelector.getSubjectKeyIdentifier() != null) + { + return new SignerId(X500Name.getInstance(certSelector.getIssuerAsBytes()), certSelector.getSerialNumber(), ASN1OctetString.getInstance(certSelector.getSubjectKeyIdentifier()).getOctets()); + } + else + { + return new SignerId(X500Name.getInstance(certSelector.getIssuerAsBytes()), certSelector.getSerialNumber()); + } + } + catch (IOException e) + { + throw new IllegalArgumentException("unable to convert issuer: " + e.getMessage()); + } + } + + public KeyTransRecipientId getKeyTransRecipientId(X509CertSelector certSelector) + { + try + { + if (certSelector.getSubjectKeyIdentifier() != null) + { + return new KeyTransRecipientId(X500Name.getInstance(certSelector.getIssuerAsBytes()), certSelector.getSerialNumber(), ASN1OctetString.getInstance(certSelector.getSubjectKeyIdentifier()).getOctets()); + } + else + { + return new KeyTransRecipientId(X500Name.getInstance(certSelector.getIssuerAsBytes()), certSelector.getSerialNumber()); + } + } + catch (IOException e) + { + throw new IllegalArgumentException("unable to convert issuer: " + e.getMessage()); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcaSignerId.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcaSignerId.java new file mode 100644 index 000000000..27934bf65 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcaSignerId.java @@ -0,0 +1,56 @@ +package org.spongycastle.cms.jcajce; + +import java.math.BigInteger; +import java.security.cert.X509Certificate; + +import javax.security.auth.x500.X500Principal; + +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.cms.SignerId; + +public class JcaSignerId + extends SignerId +{ + /** + * Construct a signer identifier based on the issuer, serial number and subject key identifier (if present) of the passed in + * certificate. + * + * @param certificate certificate providing the issue and serial number and subject key identifier. + */ + public JcaSignerId(X509Certificate certificate) + { + super(convertPrincipal(certificate.getIssuerX500Principal()), certificate.getSerialNumber(), CMSUtils.getSubjectKeyId(certificate)); + } + + /** + * Construct a signer identifier based on the provided issuer and serial number.. + * + * @param issuer the issuer to use. + * @param serialNumber the serial number to use. + */ + public JcaSignerId(X500Principal issuer, BigInteger serialNumber) + { + super(convertPrincipal(issuer), serialNumber); + } + + /** + * Construct a signer identifier based on the provided issuer, serial number, and subjectKeyId.. + * + * @param issuer the issuer to use. + * @param serialNumber the serial number to use. + * @param subjectKeyId the subject key ID to use. + */ + public JcaSignerId(X500Principal issuer, BigInteger serialNumber, byte[] subjectKeyId) + { + super(convertPrincipal(issuer), serialNumber, subjectKeyId); + } + + private static X500Name convertPrincipal(X500Principal issuer) + { + if (issuer == null) + { + return null; + } + return X500Name.getInstance(issuer.getEncoded()); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcaSignerInfoGeneratorBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcaSignerInfoGeneratorBuilder.java new file mode 100644 index 000000000..79ad9070e --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcaSignerInfoGeneratorBuilder.java @@ -0,0 +1,68 @@ +package org.spongycastle.cms.jcajce; + +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; + +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.cert.jcajce.JcaX509CertificateHolder; +import org.spongycastle.cms.CMSAttributeTableGenerator; +import org.spongycastle.cms.SignerInfoGenerator; +import org.spongycastle.cms.SignerInfoGeneratorBuilder; +import org.spongycastle.operator.ContentSigner; +import org.spongycastle.operator.DigestCalculatorProvider; +import org.spongycastle.operator.OperatorCreationException; + +public class JcaSignerInfoGeneratorBuilder +{ + private SignerInfoGeneratorBuilder builder; + + public JcaSignerInfoGeneratorBuilder(DigestCalculatorProvider digestProvider) + { + builder = new SignerInfoGeneratorBuilder(digestProvider); + } + + /** + * If the passed in flag is true, the signer signature will be based on the data, not + * a collection of signed attributes, and no signed attributes will be included. + * + * @return the builder object + */ + public JcaSignerInfoGeneratorBuilder setDirectSignature(boolean hasNoSignedAttributes) + { + builder.setDirectSignature(hasNoSignedAttributes); + + return this; + } + + public JcaSignerInfoGeneratorBuilder setSignedAttributeGenerator(CMSAttributeTableGenerator signedGen) + { + builder.setSignedAttributeGenerator(signedGen); + + return this; + } + + public JcaSignerInfoGeneratorBuilder setUnsignedAttributeGenerator(CMSAttributeTableGenerator unsignedGen) + { + builder.setUnsignedAttributeGenerator(unsignedGen); + + return this; + } + + public SignerInfoGenerator build(ContentSigner contentSigner, X509CertificateHolder certHolder) + throws OperatorCreationException + { + return builder.build(contentSigner, certHolder); + } + + public SignerInfoGenerator build(ContentSigner contentSigner, byte[] keyIdentifier) + throws OperatorCreationException + { + return builder.build(contentSigner, keyIdentifier); + } + + public SignerInfoGenerator build(ContentSigner contentSigner, X509Certificate certificate) + throws OperatorCreationException, CertificateEncodingException + { + return this.build(contentSigner, new JcaX509CertificateHolder(certificate)); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcaSignerInfoVerifierBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcaSignerInfoVerifierBuilder.java new file mode 100644 index 000000000..c50778198 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcaSignerInfoVerifierBuilder.java @@ -0,0 +1,180 @@ +package org.spongycastle.cms.jcajce; + +import java.security.Provider; +import java.security.PublicKey; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.cms.CMSSignatureAlgorithmNameGenerator; +import org.spongycastle.cms.DefaultCMSSignatureAlgorithmNameGenerator; +import org.spongycastle.cms.SignerInformationVerifier; +import org.spongycastle.operator.ContentVerifierProvider; +import org.spongycastle.operator.DefaultSignatureAlgorithmIdentifierFinder; +import org.spongycastle.operator.DigestCalculatorProvider; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.operator.SignatureAlgorithmIdentifierFinder; +import org.spongycastle.operator.jcajce.JcaContentVerifierProviderBuilder; +import org.spongycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; + +public class JcaSignerInfoVerifierBuilder +{ + private Helper helper = new Helper(); + private DigestCalculatorProvider digestProvider; + private CMSSignatureAlgorithmNameGenerator sigAlgNameGen = new DefaultCMSSignatureAlgorithmNameGenerator(); + private SignatureAlgorithmIdentifierFinder sigAlgIDFinder = new DefaultSignatureAlgorithmIdentifierFinder(); + + public JcaSignerInfoVerifierBuilder(DigestCalculatorProvider digestProvider) + { + this.digestProvider = digestProvider; + } + + public JcaSignerInfoVerifierBuilder setProvider(Provider provider) + { + this.helper = new ProviderHelper(provider); + + return this; + } + + public JcaSignerInfoVerifierBuilder setProvider(String providerName) + { + this.helper = new NamedHelper(providerName); + + return this; + } + + /** + * Override the default signature algorithm name generator. + * + * @param sigAlgNameGen the algorithm name generator to use. + * @return the current builder. + */ + public JcaSignerInfoVerifierBuilder setSignatureAlgorithmNameGenerator(CMSSignatureAlgorithmNameGenerator sigAlgNameGen) + { + this.sigAlgNameGen = sigAlgNameGen; + + return this; + } + + public JcaSignerInfoVerifierBuilder setSignatureAlgorithmFinder(SignatureAlgorithmIdentifierFinder sigAlgIDFinder) + { + this.sigAlgIDFinder = sigAlgIDFinder; + + return this; + } + + public SignerInformationVerifier build(X509CertificateHolder certHolder) + throws OperatorCreationException, CertificateException + { + return new SignerInformationVerifier(sigAlgNameGen, sigAlgIDFinder, helper.createContentVerifierProvider(certHolder), digestProvider); + } + + public SignerInformationVerifier build(X509Certificate certificate) + throws OperatorCreationException + { + return new SignerInformationVerifier(sigAlgNameGen, sigAlgIDFinder, helper.createContentVerifierProvider(certificate), digestProvider); + } + + public SignerInformationVerifier build(PublicKey pubKey) + throws OperatorCreationException + { + return new SignerInformationVerifier(sigAlgNameGen, sigAlgIDFinder, helper.createContentVerifierProvider(pubKey), digestProvider); + } + + private class Helper + { + ContentVerifierProvider createContentVerifierProvider(PublicKey publicKey) + throws OperatorCreationException + { + return new JcaContentVerifierProviderBuilder().build(publicKey); + } + + ContentVerifierProvider createContentVerifierProvider(X509Certificate certificate) + throws OperatorCreationException + { + return new JcaContentVerifierProviderBuilder().build(certificate); + } + + ContentVerifierProvider createContentVerifierProvider(X509CertificateHolder certHolder) + throws OperatorCreationException, CertificateException + { + return new JcaContentVerifierProviderBuilder().build(certHolder); + } + + DigestCalculatorProvider createDigestCalculatorProvider() + throws OperatorCreationException + { + return new JcaDigestCalculatorProviderBuilder().build(); + } + } + + private class NamedHelper + extends Helper + { + private final String providerName; + + public NamedHelper(String providerName) + { + this.providerName = providerName; + } + + ContentVerifierProvider createContentVerifierProvider(PublicKey publicKey) + throws OperatorCreationException + { + return new JcaContentVerifierProviderBuilder().setProvider(providerName).build(publicKey); + } + + ContentVerifierProvider createContentVerifierProvider(X509Certificate certificate) + throws OperatorCreationException + { + return new JcaContentVerifierProviderBuilder().setProvider(providerName).build(certificate); + } + + DigestCalculatorProvider createDigestCalculatorProvider() + throws OperatorCreationException + { + return new JcaDigestCalculatorProviderBuilder().setProvider(providerName).build(); + } + + ContentVerifierProvider createContentVerifierProvider(X509CertificateHolder certHolder) + throws OperatorCreationException, CertificateException + { + return new JcaContentVerifierProviderBuilder().setProvider(providerName).build(certHolder); + } + } + + private class ProviderHelper + extends Helper + { + private final Provider provider; + + public ProviderHelper(Provider provider) + { + this.provider = provider; + } + + ContentVerifierProvider createContentVerifierProvider(PublicKey publicKey) + throws OperatorCreationException + { + return new JcaContentVerifierProviderBuilder().setProvider(provider).build(publicKey); + } + + ContentVerifierProvider createContentVerifierProvider(X509Certificate certificate) + throws OperatorCreationException + { + return new JcaContentVerifierProviderBuilder().setProvider(provider).build(certificate); + } + + DigestCalculatorProvider createDigestCalculatorProvider() + throws OperatorCreationException + { + return new JcaDigestCalculatorProviderBuilder().setProvider(provider).build(); + } + + ContentVerifierProvider createContentVerifierProvider(X509CertificateHolder certHolder) + throws OperatorCreationException, CertificateException + { + return new JcaContentVerifierProviderBuilder().setProvider(provider).build(certHolder); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcaSimpleSignerInfoGeneratorBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcaSimpleSignerInfoGeneratorBuilder.java new file mode 100644 index 000000000..87aca5c77 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcaSimpleSignerInfoGeneratorBuilder.java @@ -0,0 +1,202 @@ +package org.spongycastle.cms.jcajce; + +import java.security.PrivateKey; +import java.security.Provider; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; + +import org.spongycastle.asn1.cms.AttributeTable; +import org.spongycastle.cert.jcajce.JcaX509CertificateHolder; +import org.spongycastle.cms.CMSAttributeTableGenerator; +import org.spongycastle.cms.DefaultSignedAttributeTableGenerator; +import org.spongycastle.cms.SignerInfoGenerator; +import org.spongycastle.cms.SignerInfoGeneratorBuilder; +import org.spongycastle.operator.ContentSigner; +import org.spongycastle.operator.DigestCalculatorProvider; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.operator.jcajce.JcaContentSignerBuilder; +import org.spongycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; + +/** + * Use this class if you are using a provider that has all the facilities you + * need. + * <p> + * For example: + * <pre> + * CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); + * ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("SC").build(signKP.getPrivate()); + * + * gen.addSignerInfoGenerator( + * new JcaSignerInfoGeneratorBuilder( + * new JcaDigestCalculatorProviderBuilder().setProvider("SC").build()) + * .build(sha1Signer, signCert)); + * </pre> + * becomes: + * <pre> + * CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); + * + * gen.addSignerInfoGenerator( + * new JcaSimpleSignerInfoGeneratorBuilder() + * .setProvider("SC") + * .build("SHA1withRSA", signKP.getPrivate(), signCert)); + * </pre> + */ +public class JcaSimpleSignerInfoGeneratorBuilder +{ + private Helper helper; + + private boolean hasNoSignedAttributes; + private CMSAttributeTableGenerator signedGen; + private CMSAttributeTableGenerator unsignedGen; + + public JcaSimpleSignerInfoGeneratorBuilder() + throws OperatorCreationException + { + this.helper = new Helper(); + } + + public JcaSimpleSignerInfoGeneratorBuilder setProvider(String providerName) + throws OperatorCreationException + { + this.helper = new NamedHelper(providerName); + + return this; + } + + public JcaSimpleSignerInfoGeneratorBuilder setProvider(Provider provider) + throws OperatorCreationException + { + this.helper = new ProviderHelper(provider); + + return this; + } + + /** + * If the passed in flag is true, the signer signature will be based on the data, not + * a collection of signed attributes, and no signed attributes will be included. + * + * @return the builder object + */ + public JcaSimpleSignerInfoGeneratorBuilder setDirectSignature(boolean hasNoSignedAttributes) + { + this.hasNoSignedAttributes = hasNoSignedAttributes; + + return this; + } + + public JcaSimpleSignerInfoGeneratorBuilder setSignedAttributeGenerator(CMSAttributeTableGenerator signedGen) + { + this.signedGen = signedGen; + + return this; + } + + /** + * set up a DefaultSignedAttributeTableGenerator primed with the passed in AttributeTable. + * + * @param attrTable table of attributes for priming generator + * @return this. + */ + public JcaSimpleSignerInfoGeneratorBuilder setSignedAttributeGenerator(AttributeTable attrTable) + { + this.signedGen = new DefaultSignedAttributeTableGenerator(attrTable); + + return this; + } + + public JcaSimpleSignerInfoGeneratorBuilder setUnsignedAttributeGenerator(CMSAttributeTableGenerator unsignedGen) + { + this.unsignedGen = unsignedGen; + + return this; + } + + public SignerInfoGenerator build(String algorithmName, PrivateKey privateKey, X509Certificate certificate) + throws OperatorCreationException, CertificateEncodingException + { + ContentSigner contentSigner = helper.createContentSigner(algorithmName, privateKey); + + return configureAndBuild().build(contentSigner, new JcaX509CertificateHolder(certificate)); + } + + public SignerInfoGenerator build(String algorithmName, PrivateKey privateKey, byte[] keyIdentifier) + throws OperatorCreationException, CertificateEncodingException + { + ContentSigner contentSigner = helper.createContentSigner(algorithmName, privateKey); + + return configureAndBuild().build(contentSigner, keyIdentifier); + } + + private SignerInfoGeneratorBuilder configureAndBuild() + throws OperatorCreationException + { + SignerInfoGeneratorBuilder infoGeneratorBuilder = new SignerInfoGeneratorBuilder(helper.createDigestCalculatorProvider()); + + infoGeneratorBuilder.setDirectSignature(hasNoSignedAttributes); + infoGeneratorBuilder.setSignedAttributeGenerator(signedGen); + infoGeneratorBuilder.setUnsignedAttributeGenerator(unsignedGen); + + return infoGeneratorBuilder; + } + + private class Helper + { + ContentSigner createContentSigner(String algorithm, PrivateKey privateKey) + throws OperatorCreationException + { + return new JcaContentSignerBuilder(algorithm).build(privateKey); + } + + DigestCalculatorProvider createDigestCalculatorProvider() + throws OperatorCreationException + { + return new JcaDigestCalculatorProviderBuilder().build(); + } + } + + private class NamedHelper + extends Helper + { + private final String providerName; + + public NamedHelper(String providerName) + { + this.providerName = providerName; + } + + ContentSigner createContentSigner(String algorithm, PrivateKey privateKey) + throws OperatorCreationException + { + return new JcaContentSignerBuilder(algorithm).setProvider(providerName).build(privateKey); + } + + DigestCalculatorProvider createDigestCalculatorProvider() + throws OperatorCreationException + { + return new JcaDigestCalculatorProviderBuilder().setProvider(providerName).build(); + } + } + + private class ProviderHelper + extends Helper + { + private final Provider provider; + + public ProviderHelper(Provider provider) + { + this.provider = provider; + } + + ContentSigner createContentSigner(String algorithm, PrivateKey privateKey) + throws OperatorCreationException + { + return new JcaContentSignerBuilder(algorithm).setProvider(provider).build(privateKey); + } + + DigestCalculatorProvider createDigestCalculatorProvider() + throws OperatorCreationException + { + return new JcaDigestCalculatorProviderBuilder().setProvider(provider).build(); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcaSimpleSignerInfoVerifierBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcaSimpleSignerInfoVerifierBuilder.java new file mode 100644 index 000000000..4463b0659 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcaSimpleSignerInfoVerifierBuilder.java @@ -0,0 +1,150 @@ +package org.spongycastle.cms.jcajce; + +import java.security.Provider; +import java.security.PublicKey; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.cms.DefaultCMSSignatureAlgorithmNameGenerator; +import org.spongycastle.cms.SignerInformationVerifier; +import org.spongycastle.operator.ContentVerifierProvider; +import org.spongycastle.operator.DefaultSignatureAlgorithmIdentifierFinder; +import org.spongycastle.operator.DigestCalculatorProvider; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.operator.jcajce.JcaContentVerifierProviderBuilder; +import org.spongycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; + +public class JcaSimpleSignerInfoVerifierBuilder +{ + private Helper helper = new Helper(); + + public JcaSimpleSignerInfoVerifierBuilder setProvider(Provider provider) + { + this.helper = new ProviderHelper(provider); + + return this; + } + + public JcaSimpleSignerInfoVerifierBuilder setProvider(String providerName) + { + this.helper = new NamedHelper(providerName); + + return this; + } + + public SignerInformationVerifier build(X509CertificateHolder certHolder) + throws OperatorCreationException, CertificateException + { + return new SignerInformationVerifier(new DefaultCMSSignatureAlgorithmNameGenerator(), new DefaultSignatureAlgorithmIdentifierFinder(), helper.createContentVerifierProvider(certHolder), helper.createDigestCalculatorProvider()); + } + + public SignerInformationVerifier build(X509Certificate certificate) + throws OperatorCreationException + { + return new SignerInformationVerifier(new DefaultCMSSignatureAlgorithmNameGenerator(), new DefaultSignatureAlgorithmIdentifierFinder(), helper.createContentVerifierProvider(certificate), helper.createDigestCalculatorProvider()); + } + + public SignerInformationVerifier build(PublicKey pubKey) + throws OperatorCreationException + { + return new SignerInformationVerifier(new DefaultCMSSignatureAlgorithmNameGenerator(), new DefaultSignatureAlgorithmIdentifierFinder(), helper.createContentVerifierProvider(pubKey), helper.createDigestCalculatorProvider()); + } + + private class Helper + { + ContentVerifierProvider createContentVerifierProvider(PublicKey publicKey) + throws OperatorCreationException + { + return new JcaContentVerifierProviderBuilder().build(publicKey); + } + + ContentVerifierProvider createContentVerifierProvider(X509Certificate certificate) + throws OperatorCreationException + { + return new JcaContentVerifierProviderBuilder().build(certificate); + } + + ContentVerifierProvider createContentVerifierProvider(X509CertificateHolder certHolder) + throws OperatorCreationException, CertificateException + { + return new JcaContentVerifierProviderBuilder().build(certHolder); + } + + DigestCalculatorProvider createDigestCalculatorProvider() + throws OperatorCreationException + { + return new JcaDigestCalculatorProviderBuilder().build(); + } + } + + private class NamedHelper + extends Helper + { + private final String providerName; + + public NamedHelper(String providerName) + { + this.providerName = providerName; + } + + ContentVerifierProvider createContentVerifierProvider(PublicKey publicKey) + throws OperatorCreationException + { + return new JcaContentVerifierProviderBuilder().setProvider(providerName).build(publicKey); + } + + ContentVerifierProvider createContentVerifierProvider(X509Certificate certificate) + throws OperatorCreationException + { + return new JcaContentVerifierProviderBuilder().setProvider(providerName).build(certificate); + } + + DigestCalculatorProvider createDigestCalculatorProvider() + throws OperatorCreationException + { + return new JcaDigestCalculatorProviderBuilder().setProvider(providerName).build(); + } + + ContentVerifierProvider createContentVerifierProvider(X509CertificateHolder certHolder) + throws OperatorCreationException, CertificateException + { + return new JcaContentVerifierProviderBuilder().setProvider(providerName).build(certHolder); + } + } + + private class ProviderHelper + extends Helper + { + private final Provider provider; + + public ProviderHelper(Provider provider) + { + this.provider = provider; + } + + ContentVerifierProvider createContentVerifierProvider(PublicKey publicKey) + throws OperatorCreationException + { + return new JcaContentVerifierProviderBuilder().setProvider(provider).build(publicKey); + } + + ContentVerifierProvider createContentVerifierProvider(X509Certificate certificate) + throws OperatorCreationException + { + return new JcaContentVerifierProviderBuilder().setProvider(provider).build(certificate); + } + + DigestCalculatorProvider createDigestCalculatorProvider() + throws OperatorCreationException + { + return new JcaDigestCalculatorProviderBuilder().setProvider(provider).build(); + } + + ContentVerifierProvider createContentVerifierProvider(X509CertificateHolder certHolder) + throws OperatorCreationException, CertificateException + { + return new JcaContentVerifierProviderBuilder().setProvider(provider).build(certHolder); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcaX509CertSelectorConverter.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcaX509CertSelectorConverter.java new file mode 100644 index 000000000..ceb138ed6 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcaX509CertSelectorConverter.java @@ -0,0 +1,24 @@ +package org.spongycastle.cms.jcajce; + +import java.security.cert.X509CertSelector; + +import org.spongycastle.cms.KeyTransRecipientId; +import org.spongycastle.cms.SignerId; + +public class JcaX509CertSelectorConverter + extends org.spongycastle.cert.selector.jcajce.JcaX509CertSelectorConverter +{ + public JcaX509CertSelectorConverter() + { + } + + public X509CertSelector getCertSelector(KeyTransRecipientId recipientId) + { + return doConversion(recipientId.getIssuer(), recipientId.getSerialNumber(), recipientId.getSubjectKeyIdentifier()); + } + + public X509CertSelector getCertSelector(SignerId signerId) + { + return doConversion(signerId.getIssuer(), signerId.getSerialNumber(), signerId.getSubjectKeyIdentifier()); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceAlgorithmIdentifierConverter.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceAlgorithmIdentifierConverter.java new file mode 100644 index 000000000..32007919e --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceAlgorithmIdentifierConverter.java @@ -0,0 +1,64 @@ +package org.spongycastle.cms.jcajce; + + +import java.security.AlgorithmParameters; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Provider; +import java.security.SecureRandom; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cms.CMSException; + +public class JceAlgorithmIdentifierConverter +{ + private EnvelopedDataHelper helper = new EnvelopedDataHelper(new DefaultJcaJceExtHelper()); + private SecureRandom random; + + public JceAlgorithmIdentifierConverter() + { + } + + public JceAlgorithmIdentifierConverter setProvider(Provider provider) + { + this.helper = new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider)); + + return this; + } + + public JceAlgorithmIdentifierConverter setProvider(String providerName) + { + this.helper = new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName)); + + return this; + } + + public AlgorithmParameters getAlgorithmParameters(AlgorithmIdentifier algorithmIdentifier) + throws CMSException + { + ASN1Encodable parameters = algorithmIdentifier.getParameters(); + + if (parameters == null) + { + return null; + } + + try + { + AlgorithmParameters params = helper.createAlgorithmParameters(algorithmIdentifier.getAlgorithm()); + + CMSUtils.loadParameters(params, algorithmIdentifier.getParameters()); + + return params; + } + catch (NoSuchAlgorithmException e) + { + throw new CMSException("can't find parameters for algorithm", e); + } + catch (NoSuchProviderException e) + { + throw new CMSException("can't find provider for algorithm", e); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceCMSContentEncryptorBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceCMSContentEncryptorBuilder.java new file mode 100644 index 000000000..5711df3d9 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceCMSContentEncryptorBuilder.java @@ -0,0 +1,160 @@ +package org.spongycastle.cms.jcajce; + +import java.io.OutputStream; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.Provider; +import java.security.SecureRandom; + +import javax.crypto.Cipher; +import javax.crypto.CipherOutputStream; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cms.CMSException; +import org.spongycastle.operator.DefaultSecretKeySizeProvider; +import org.spongycastle.operator.GenericKey; +import org.spongycastle.operator.OutputEncryptor; +import org.spongycastle.operator.SecretKeySizeProvider; +import org.spongycastle.operator.jcajce.JceGenericKey; + +public class JceCMSContentEncryptorBuilder +{ + private static final SecretKeySizeProvider KEY_SIZE_PROVIDER = DefaultSecretKeySizeProvider.INSTANCE; + + + private final ASN1ObjectIdentifier encryptionOID; + private final int keySize; + + private EnvelopedDataHelper helper = new EnvelopedDataHelper(new DefaultJcaJceExtHelper()); + private SecureRandom random; + + public JceCMSContentEncryptorBuilder(ASN1ObjectIdentifier encryptionOID) + { + this(encryptionOID, KEY_SIZE_PROVIDER.getKeySize(encryptionOID)); + } + + public JceCMSContentEncryptorBuilder(ASN1ObjectIdentifier encryptionOID, int keySize) + { + this.encryptionOID = encryptionOID; + this.keySize = keySize; + + int fixedSize = KEY_SIZE_PROVIDER.getKeySize(encryptionOID); + + if (encryptionOID.equals(PKCSObjectIdentifiers.des_EDE3_CBC)) + { + if (keySize != 168 && keySize != fixedSize) + { + throw new IllegalArgumentException("incorrect keySize for encryptionOID passed to builder."); + } + } + else + { + if (fixedSize > 0 && fixedSize != keySize) + { + throw new IllegalArgumentException("incorrect keySize for encryptionOID passed to builder."); + } + } + } + + public JceCMSContentEncryptorBuilder setProvider(Provider provider) + { + this.helper = new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider)); + + return this; + } + + public JceCMSContentEncryptorBuilder setProvider(String providerName) + { + this.helper = new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName)); + + return this; + } + + public JceCMSContentEncryptorBuilder setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + public OutputEncryptor build() + throws CMSException + { + return new CMSOutputEncryptor(encryptionOID, keySize, random); + } + + private class CMSOutputEncryptor + implements OutputEncryptor + { + private SecretKey encKey; + private AlgorithmIdentifier algorithmIdentifier; + private Cipher cipher; + + CMSOutputEncryptor(ASN1ObjectIdentifier encryptionOID, int keySize, SecureRandom random) + throws CMSException + { + KeyGenerator keyGen = helper.createKeyGenerator(encryptionOID); + + if (random == null) + { + random = new SecureRandom(); + } + + if (keySize < 0) + { + keyGen.init(random); + } + else + { + if (encryptionOID.equals(PKCSObjectIdentifiers.des_EDE3_CBC) && keySize == 192) + { + keySize = 168; + } + keyGen.init(keySize, random); + } + + cipher = helper.createCipher(encryptionOID); + encKey = keyGen.generateKey(); + AlgorithmParameters params = helper.generateParameters(encryptionOID, encKey, random); + + try + { + cipher.init(Cipher.ENCRYPT_MODE, encKey, params, random); + } + catch (GeneralSecurityException e) + { + throw new CMSException("unable to initialize cipher: " + e.getMessage(), e); + } + + // + // If params are null we try and second guess on them as some providers don't provide + // algorithm parameter generation explicity but instead generate them under the hood. + // + if (params == null) + { + params = cipher.getParameters(); + } + + algorithmIdentifier = helper.getAlgorithmIdentifier(encryptionOID, params); + } + + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return algorithmIdentifier; + } + + public OutputStream getOutputStream(OutputStream dOut) + { + return new CipherOutputStream(dOut, cipher); + } + + public GenericKey getKey() + { + return new JceGenericKey(algorithmIdentifier, encKey); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceCMSMacCalculatorBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceCMSMacCalculatorBuilder.java new file mode 100644 index 000000000..d60974713 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceCMSMacCalculatorBuilder.java @@ -0,0 +1,155 @@ +package org.spongycastle.cms.jcajce; + +import java.io.OutputStream; +import java.security.AlgorithmParameterGenerator; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.Provider; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; + +import javax.crypto.KeyGenerator; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.RC2ParameterSpec; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cms.CMSException; +import org.spongycastle.jcajce.io.MacOutputStream; +import org.spongycastle.operator.GenericKey; +import org.spongycastle.operator.MacCalculator; +import org.spongycastle.operator.jcajce.JceGenericKey; + +public class JceCMSMacCalculatorBuilder +{ + private final ASN1ObjectIdentifier macOID; + private final int keySize; + + private EnvelopedDataHelper helper = new EnvelopedDataHelper(new DefaultJcaJceExtHelper()); + private SecureRandom random; + + public JceCMSMacCalculatorBuilder(ASN1ObjectIdentifier macOID) + { + this(macOID, -1); + } + + public JceCMSMacCalculatorBuilder(ASN1ObjectIdentifier macOID, int keySize) + { + this.macOID = macOID; + this.keySize = keySize; + } + + public JceCMSMacCalculatorBuilder setProvider(Provider provider) + { + this.helper = new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider)); + + return this; + } + + public JceCMSMacCalculatorBuilder setProvider(String providerName) + { + this.helper = new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName)); + + return this; + } + + public JceCMSMacCalculatorBuilder setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + public MacCalculator build() + throws CMSException + { + return new CMSMacCalculator(macOID, keySize, random); + } + + private class CMSMacCalculator + implements MacCalculator + { + private SecretKey encKey; + private AlgorithmIdentifier algorithmIdentifier; + private Mac mac; + private SecureRandom random; + + CMSMacCalculator(ASN1ObjectIdentifier macOID, int keySize, SecureRandom random) + throws CMSException + { + KeyGenerator keyGen = helper.createKeyGenerator(macOID); + + if (random == null) + { + random = new SecureRandom(); + } + + this.random = random; + + if (keySize < 0) + { + keyGen.init(random); + } + else + { + keyGen.init(keySize, random); + } + + encKey = keyGen.generateKey(); + + AlgorithmParameterSpec paramSpec = generateParameterSpec(macOID, encKey); + + algorithmIdentifier = helper.getAlgorithmIdentifier(macOID, paramSpec); + mac = helper.createContentMac(encKey, algorithmIdentifier); + } + + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return algorithmIdentifier; + } + + public OutputStream getOutputStream() + { + return new MacOutputStream(mac); + } + + public byte[] getMac() + { + return mac.doFinal(); + } + + public GenericKey getKey() + { + return new JceGenericKey(algorithmIdentifier, encKey); + } + + protected AlgorithmParameterSpec generateParameterSpec(ASN1ObjectIdentifier macOID, SecretKey encKey) + throws CMSException + { + try + { + if (macOID.equals(PKCSObjectIdentifiers.RC2_CBC)) + { + byte[] iv = new byte[8]; + + random.nextBytes(iv); + + return new RC2ParameterSpec(encKey.getEncoded().length * 8, iv); + } + + AlgorithmParameterGenerator pGen = helper.createAlgorithmParameterGenerator(macOID); + + AlgorithmParameters p = pGen.generateParameters(); + + return p.getParameterSpec(IvParameterSpec.class); + } + catch (GeneralSecurityException e) + { + return null; + } + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKEKAuthenticatedRecipient.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKEKAuthenticatedRecipient.java new file mode 100644 index 000000000..55a19d548 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKEKAuthenticatedRecipient.java @@ -0,0 +1,61 @@ +package org.spongycastle.cms.jcajce; + +import java.io.OutputStream; +import java.security.Key; + +import javax.crypto.Mac; +import javax.crypto.SecretKey; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cms.CMSException; +import org.spongycastle.cms.RecipientOperator; +import org.spongycastle.jcajce.io.MacOutputStream; +import org.spongycastle.operator.GenericKey; +import org.spongycastle.operator.MacCalculator; +import org.spongycastle.operator.jcajce.JceGenericKey; + + +/** + * the KeyTransRecipientInformation class for a recipient who has been sent a secret + * key encrypted using their public key that needs to be used to + * extract the message. + */ +public class JceKEKAuthenticatedRecipient + extends JceKEKRecipient +{ + public JceKEKAuthenticatedRecipient(SecretKey recipientKey) + { + super(recipientKey); + } + + public RecipientOperator getRecipientOperator(AlgorithmIdentifier keyEncryptionAlgorithm, final AlgorithmIdentifier contentMacAlgorithm, byte[] encryptedContentEncryptionKey) + throws CMSException + { + final Key secretKey = extractSecretKey(keyEncryptionAlgorithm, contentMacAlgorithm, encryptedContentEncryptionKey); + + final Mac dataMac = contentHelper.createContentMac(secretKey, contentMacAlgorithm); + + return new RecipientOperator(new MacCalculator() + { + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return contentMacAlgorithm; + } + + public GenericKey getKey() + { + return new JceGenericKey(contentMacAlgorithm, secretKey); + } + + public OutputStream getOutputStream() + { + return new MacOutputStream(dataMac); + } + + public byte[] getMac() + { + return dataMac.doFinal(); + } + }); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKEKEnvelopedRecipient.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKEKEnvelopedRecipient.java new file mode 100644 index 000000000..8bf940064 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKEKEnvelopedRecipient.java @@ -0,0 +1,43 @@ +package org.spongycastle.cms.jcajce; + +import java.io.InputStream; +import java.security.Key; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.SecretKey; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cms.CMSException; +import org.spongycastle.cms.RecipientOperator; +import org.spongycastle.operator.InputDecryptor; + +public class JceKEKEnvelopedRecipient + extends JceKEKRecipient +{ + public JceKEKEnvelopedRecipient(SecretKey recipientKey) + { + super(recipientKey); + } + + public RecipientOperator getRecipientOperator(AlgorithmIdentifier keyEncryptionAlgorithm, final AlgorithmIdentifier contentEncryptionAlgorithm, byte[] encryptedContentEncryptionKey) + throws CMSException + { + Key secretKey = extractSecretKey(keyEncryptionAlgorithm, contentEncryptionAlgorithm, encryptedContentEncryptionKey); + + final Cipher dataCipher = contentHelper.createContentCipher(secretKey, contentEncryptionAlgorithm); + + return new RecipientOperator(new InputDecryptor() + { + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return contentEncryptionAlgorithm; + } + + public InputStream getInputStream(InputStream dataOut) + { + return new CipherInputStream(dataOut, dataCipher); + } + }); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKEKRecipient.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKEKRecipient.java new file mode 100644 index 000000000..84ad638f9 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKEKRecipient.java @@ -0,0 +1,119 @@ +package org.spongycastle.cms.jcajce; + +import java.security.Key; +import java.security.Provider; + +import javax.crypto.SecretKey; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cms.CMSException; +import org.spongycastle.cms.KEKRecipient; +import org.spongycastle.operator.OperatorException; +import org.spongycastle.operator.SymmetricKeyUnwrapper; + +public abstract class JceKEKRecipient + implements KEKRecipient +{ + private SecretKey recipientKey; + + protected EnvelopedDataHelper helper = new EnvelopedDataHelper(new DefaultJcaJceExtHelper()); + protected EnvelopedDataHelper contentHelper = helper; + protected boolean validateKeySize = false; + + public JceKEKRecipient(SecretKey recipientKey) + { + this.recipientKey = recipientKey; + } + + /** + * Set the provider to use for key recovery and content processing. + * + * @param provider provider to use. + * @return this recipient. + */ + public JceKEKRecipient setProvider(Provider provider) + { + this.helper = new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider)); + this.contentHelper = helper; + + return this; + } + + /** + * Set the provider to use for key recovery and content processing. + * + * @param providerName the name of the provider to use. + * @return this recipient. + */ + public JceKEKRecipient setProvider(String providerName) + { + this.helper = new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName)); + this.contentHelper = helper; + + return this; + } + + /** + * Set the provider to use for content processing. + * + * @param provider the provider to use. + * @return this recipient. + */ + public JceKEKRecipient setContentProvider(Provider provider) + { + this.contentHelper = new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider)); + + return this; + } + + /** + * Set the provider to use for content processing. + * + * @param providerName the name of the provider to use. + * @return this recipient. + */ + public JceKEKRecipient setContentProvider(String providerName) + { + this.contentHelper = new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName)); + + return this; + } + + /** + * Set validation of retrieved key sizes against the algorithm parameters for the encrypted key where possible - default is off. + * <p> + * This setting will not have any affect if the encryption algorithm in the recipient does not specify a particular key size, or + * if the unwrapper is a HSM and the byte encoding of the unwrapped secret key is not available. + * </p> + * @param doValidate true if unwrapped key's should be validated against the content encryption algorithm, false otherwise. + * @return this recipient. + */ + public JceKEKRecipient setKeySizeValidation(boolean doValidate) + { + this.validateKeySize = doValidate; + + return this; + } + + protected Key extractSecretKey(AlgorithmIdentifier keyEncryptionAlgorithm, AlgorithmIdentifier encryptedKeyAlgorithm, byte[] encryptedContentEncryptionKey) + throws CMSException + { + SymmetricKeyUnwrapper unwrapper = helper.createSymmetricUnwrapper(keyEncryptionAlgorithm, recipientKey); + + try + { + Key key = helper.getJceKey(encryptedKeyAlgorithm.getAlgorithm(), unwrapper.generateUnwrappedKey(encryptedKeyAlgorithm, encryptedContentEncryptionKey)); + + if (validateKeySize) + { + helper.keySizeCheck(encryptedKeyAlgorithm, key); + } + + return key; + } + catch (OperatorException e) + { + throw new CMSException("exception unwrapping key: " + e.getMessage(), e); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKEKRecipientInfoGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKEKRecipientInfoGenerator.java new file mode 100644 index 000000000..7a67de4a9 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKEKRecipientInfoGenerator.java @@ -0,0 +1,45 @@ +package org.spongycastle.cms.jcajce; + +import java.security.Provider; +import java.security.SecureRandom; + +import javax.crypto.SecretKey; + +import org.spongycastle.asn1.cms.KEKIdentifier; +import org.spongycastle.cms.KEKRecipientInfoGenerator; +import org.spongycastle.operator.jcajce.JceSymmetricKeyWrapper; + +public class JceKEKRecipientInfoGenerator + extends KEKRecipientInfoGenerator +{ + public JceKEKRecipientInfoGenerator(KEKIdentifier kekIdentifier, SecretKey keyEncryptionKey) + { + super(kekIdentifier, new JceSymmetricKeyWrapper(keyEncryptionKey)); + } + + public JceKEKRecipientInfoGenerator(byte[] keyIdentifier, SecretKey keyEncryptionKey) + { + this(new KEKIdentifier(keyIdentifier, null, null), keyEncryptionKey); + } + + public JceKEKRecipientInfoGenerator setProvider(Provider provider) + { + ((JceSymmetricKeyWrapper)this.wrapper).setProvider(provider); + + return this; + } + + public JceKEKRecipientInfoGenerator setProvider(String providerName) + { + ((JceSymmetricKeyWrapper)this.wrapper).setProvider(providerName); + + return this; + } + + public JceKEKRecipientInfoGenerator setSecureRandom(SecureRandom random) + { + ((JceSymmetricKeyWrapper)this.wrapper).setSecureRandom(random); + + return this; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKeyAgreeAuthenticatedRecipient.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKeyAgreeAuthenticatedRecipient.java new file mode 100644 index 000000000..3186b561c --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKeyAgreeAuthenticatedRecipient.java @@ -0,0 +1,57 @@ +package org.spongycastle.cms.jcajce; + +import java.io.OutputStream; +import java.security.Key; +import java.security.PrivateKey; + +import javax.crypto.Mac; + +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.cms.CMSException; +import org.spongycastle.cms.RecipientOperator; +import org.spongycastle.jcajce.io.MacOutputStream; +import org.spongycastle.operator.GenericKey; +import org.spongycastle.operator.MacCalculator; +import org.spongycastle.operator.jcajce.JceGenericKey; + +public class JceKeyAgreeAuthenticatedRecipient + extends JceKeyAgreeRecipient +{ + public JceKeyAgreeAuthenticatedRecipient(PrivateKey recipientKey) + { + super(recipientKey); + } + + public RecipientOperator getRecipientOperator(AlgorithmIdentifier keyEncryptionAlgorithm, final AlgorithmIdentifier contentMacAlgorithm, SubjectPublicKeyInfo senderPublicKey, ASN1OctetString userKeyingMaterial, byte[] encryptedContentKey) + throws CMSException + { + final Key secretKey = extractSecretKey(keyEncryptionAlgorithm, contentMacAlgorithm, senderPublicKey, userKeyingMaterial, encryptedContentKey); + + final Mac dataMac = contentHelper.createContentMac(secretKey, contentMacAlgorithm); + + return new RecipientOperator(new MacCalculator() + { + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return contentMacAlgorithm; + } + + public GenericKey getKey() + { + return new JceGenericKey(contentMacAlgorithm, secretKey); + } + + public OutputStream getOutputStream() + { + return new MacOutputStream(dataMac); + } + + public byte[] getMac() + { + return dataMac.doFinal(); + } + }); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKeyAgreeEnvelopedRecipient.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKeyAgreeEnvelopedRecipient.java new file mode 100644 index 000000000..274d8ed9c --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKeyAgreeEnvelopedRecipient.java @@ -0,0 +1,45 @@ +package org.spongycastle.cms.jcajce; + +import java.io.InputStream; +import java.security.Key; +import java.security.PrivateKey; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; + +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.cms.CMSException; +import org.spongycastle.cms.RecipientOperator; +import org.spongycastle.operator.InputDecryptor; + +public class JceKeyAgreeEnvelopedRecipient + extends JceKeyAgreeRecipient +{ + public JceKeyAgreeEnvelopedRecipient(PrivateKey recipientKey) + { + super(recipientKey); + } + + public RecipientOperator getRecipientOperator(AlgorithmIdentifier keyEncryptionAlgorithm, final AlgorithmIdentifier contentEncryptionAlgorithm, SubjectPublicKeyInfo senderPublicKey, ASN1OctetString userKeyingMaterial, byte[] encryptedContentKey) + throws CMSException + { + Key secretKey = extractSecretKey(keyEncryptionAlgorithm, contentEncryptionAlgorithm, senderPublicKey, userKeyingMaterial, encryptedContentKey); + + final Cipher dataCipher = contentHelper.createContentCipher(secretKey, contentEncryptionAlgorithm); + + return new RecipientOperator(new InputDecryptor() + { + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return contentEncryptionAlgorithm; + } + + public InputStream getInputStream(InputStream dataOut) + { + return new CipherInputStream(dataOut, dataCipher); + } + }); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKeyAgreeRecipient.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKeyAgreeRecipient.java new file mode 100644 index 000000000..1beea382c --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKeyAgreeRecipient.java @@ -0,0 +1,184 @@ +package org.spongycastle.cms.jcajce; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; + +import javax.crypto.Cipher; +import javax.crypto.KeyAgreement; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.cms.ecc.MQVuserKeyingMaterial; +import org.spongycastle.asn1.pkcs.PrivateKeyInfo; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.cms.CMSEnvelopedGenerator; +import org.spongycastle.cms.CMSException; +import org.spongycastle.cms.KeyAgreeRecipient; +import org.spongycastle.jce.spec.MQVPrivateKeySpec; +import org.spongycastle.jce.spec.MQVPublicKeySpec; + +public abstract class JceKeyAgreeRecipient + implements KeyAgreeRecipient +{ + private PrivateKey recipientKey; + protected EnvelopedDataHelper helper = new EnvelopedDataHelper(new DefaultJcaJceExtHelper()); + protected EnvelopedDataHelper contentHelper = helper; + + public JceKeyAgreeRecipient(PrivateKey recipientKey) + { + this.recipientKey = recipientKey; + } + + /** + * Set the provider to use for key recovery and content processing. + * + * @param provider provider to use. + * @return this recipient. + */ + public JceKeyAgreeRecipient setProvider(Provider provider) + { + this.helper = new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider)); + this.contentHelper = helper; + + return this; + } + + /** + * Set the provider to use for key recovery and content processing. + * + * @param providerName the name of the provider to use. + * @return this recipient. + */ + public JceKeyAgreeRecipient setProvider(String providerName) + { + this.helper = new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName)); + this.contentHelper = helper; + + return this; + } + + /** + * Set the provider to use for content processing. If providerName is null a "no provider" search will be + * used to satisfy getInstance calls. + * + * @param provider the provider to use. + * @return this recipient. + */ + public JceKeyAgreeRecipient setContentProvider(Provider provider) + { + this.contentHelper = CMSUtils.createContentHelper(provider); + + return this; + } + + /** + * Set the provider to use for content processing. If providerName is null a "no provider" search will be + * used to satisfy getInstance calls. + * + * @param providerName the name of the provider to use. + * @return this recipient. + */ + public JceKeyAgreeRecipient setContentProvider(String providerName) + { + this.contentHelper = CMSUtils.createContentHelper(providerName); + + return this; + } + + private SecretKey calculateAgreedWrapKey(AlgorithmIdentifier keyEncAlg, ASN1ObjectIdentifier wrapAlg, + PublicKey senderPublicKey, ASN1OctetString userKeyingMaterial, PrivateKey receiverPrivateKey) + throws CMSException, GeneralSecurityException, IOException + { + String agreeAlg = keyEncAlg.getAlgorithm().getId(); + + if (agreeAlg.equals(CMSEnvelopedGenerator.ECMQV_SHA1KDF)) + { + byte[] ukmEncoding = userKeyingMaterial.getOctets(); + MQVuserKeyingMaterial ukm = MQVuserKeyingMaterial.getInstance( + ASN1Primitive.fromByteArray(ukmEncoding)); + + SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo( + getPrivateKeyAlgorithmIdentifier(), + ukm.getEphemeralPublicKey().getPublicKey().getBytes()); + + X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(pubInfo.getEncoded()); + KeyFactory fact = helper.createKeyFactory(keyEncAlg.getAlgorithm()); + PublicKey ephemeralKey = fact.generatePublic(pubSpec); + + senderPublicKey = new MQVPublicKeySpec(senderPublicKey, ephemeralKey); + receiverPrivateKey = new MQVPrivateKeySpec(receiverPrivateKey, receiverPrivateKey); + } + + KeyAgreement agreement = helper.createKeyAgreement(keyEncAlg.getAlgorithm()); + + agreement.init(receiverPrivateKey); + agreement.doPhase(senderPublicKey, true); + + return agreement.generateSecret(wrapAlg.getId()); + } + + private Key unwrapSessionKey(ASN1ObjectIdentifier wrapAlg, SecretKey agreedKey, ASN1ObjectIdentifier contentEncryptionAlgorithm, byte[] encryptedContentEncryptionKey) + throws CMSException, InvalidKeyException, NoSuchAlgorithmException + { + Cipher keyCipher = helper.createCipher(wrapAlg); + keyCipher.init(Cipher.UNWRAP_MODE, agreedKey); + return keyCipher.unwrap(encryptedContentEncryptionKey, helper.getBaseCipherName(contentEncryptionAlgorithm), Cipher.SECRET_KEY); + } + + protected Key extractSecretKey(AlgorithmIdentifier keyEncryptionAlgorithm, AlgorithmIdentifier contentEncryptionAlgorithm, SubjectPublicKeyInfo senderKey, ASN1OctetString userKeyingMaterial, byte[] encryptedContentEncryptionKey) + throws CMSException + { + try + { + ASN1ObjectIdentifier wrapAlg = + AlgorithmIdentifier.getInstance(keyEncryptionAlgorithm.getParameters()).getAlgorithm(); + + X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(senderKey.getEncoded()); + KeyFactory fact = helper.createKeyFactory(keyEncryptionAlgorithm.getAlgorithm()); + PublicKey senderPublicKey = fact.generatePublic(pubSpec); + + SecretKey agreedWrapKey = calculateAgreedWrapKey(keyEncryptionAlgorithm, wrapAlg, + senderPublicKey, userKeyingMaterial, recipientKey); + + return unwrapSessionKey(wrapAlg, agreedWrapKey, contentEncryptionAlgorithm.getAlgorithm(), encryptedContentEncryptionKey); + } + catch (NoSuchAlgorithmException e) + { + throw new CMSException("can't find algorithm.", e); + } + catch (InvalidKeyException e) + { + throw new CMSException("key invalid in message.", e); + } + catch (InvalidKeySpecException e) + { + throw new CMSException("originator key spec invalid.", e); + } + catch (NoSuchPaddingException e) + { + throw new CMSException("required padding not supported.", e); + } + catch (Exception e) + { + throw new CMSException("originator key invalid.", e); + } + } + + public AlgorithmIdentifier getPrivateKeyAlgorithmIdentifier() + { + return PrivateKeyInfo.getInstance(recipientKey.getEncoded()).getPrivateKeyAlgorithm(); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKeyAgreeRecipientId.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKeyAgreeRecipientId.java new file mode 100644 index 000000000..1774b394c --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKeyAgreeRecipientId.java @@ -0,0 +1,23 @@ +package org.spongycastle.cms.jcajce; + +import java.math.BigInteger; +import java.security.cert.X509Certificate; + +import javax.security.auth.x500.X500Principal; + +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.cms.KeyAgreeRecipientId; + +public class JceKeyAgreeRecipientId + extends KeyAgreeRecipientId +{ + public JceKeyAgreeRecipientId(X509Certificate certificate) + { + this(certificate.getIssuerX500Principal(), certificate.getSerialNumber()); + } + + public JceKeyAgreeRecipientId(X500Principal issuer, BigInteger serialNumber) + { + super(X500Name.getInstance(issuer.getEncoded()), serialNumber); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKeyAgreeRecipientInfoGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKeyAgreeRecipientInfoGenerator.java new file mode 100644 index 000000000..eb9699f5c --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKeyAgreeRecipientInfoGenerator.java @@ -0,0 +1,215 @@ +package org.spongycastle.cms.jcajce; + +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECParameterSpec; +import java.util.ArrayList; +import java.util.List; + +import javax.crypto.Cipher; +import javax.crypto.KeyAgreement; +import javax.crypto.SecretKey; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.asn1.cms.KeyAgreeRecipientIdentifier; +import org.spongycastle.asn1.cms.RecipientEncryptedKey; +import org.spongycastle.asn1.cms.RecipientKeyIdentifier; +import org.spongycastle.asn1.cms.ecc.MQVuserKeyingMaterial; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.cms.CMSAlgorithm; +import org.spongycastle.cms.CMSEnvelopedGenerator; +import org.spongycastle.cms.CMSException; +import org.spongycastle.cms.KeyAgreeRecipientInfoGenerator; +import org.spongycastle.jce.spec.MQVPrivateKeySpec; +import org.spongycastle.jce.spec.MQVPublicKeySpec; +import org.spongycastle.operator.GenericKey; + +public class JceKeyAgreeRecipientInfoGenerator + extends KeyAgreeRecipientInfoGenerator +{ + private List recipientIDs = new ArrayList(); + private List recipientKeys = new ArrayList(); + private PublicKey senderPublicKey; + private PrivateKey senderPrivateKey; + + private EnvelopedDataHelper helper = new EnvelopedDataHelper(new DefaultJcaJceExtHelper()); + private SecureRandom random; + private KeyPair ephemeralKP; + + public JceKeyAgreeRecipientInfoGenerator(ASN1ObjectIdentifier keyAgreementOID, PrivateKey senderPrivateKey, PublicKey senderPublicKey, ASN1ObjectIdentifier keyEncryptionOID) + { + super(keyAgreementOID, SubjectPublicKeyInfo.getInstance(senderPublicKey.getEncoded()), keyEncryptionOID); + + this.senderPublicKey = senderPublicKey; + this.senderPrivateKey = senderPrivateKey; + } + + public JceKeyAgreeRecipientInfoGenerator setProvider(Provider provider) + { + this.helper = new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider)); + + return this; + } + + public JceKeyAgreeRecipientInfoGenerator setProvider(String providerName) + { + this.helper = new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName)); + + return this; + } + + public JceKeyAgreeRecipientInfoGenerator setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + /** + * Add a recipient based on the passed in certificate's public key and its issuer and serial number. + * + * @param recipientCert recipient's certificate + * @return the current instance. + * @throws CertificateEncodingException if the necessary data cannot be extracted from the certificate. + */ + public JceKeyAgreeRecipientInfoGenerator addRecipient(X509Certificate recipientCert) + throws CertificateEncodingException + { + recipientIDs.add(new KeyAgreeRecipientIdentifier(CMSUtils.getIssuerAndSerialNumber(recipientCert))); + recipientKeys.add(recipientCert.getPublicKey()); + + return this; + } + + /** + * Add a recipient identified by the passed in subjectKeyID and the for the passed in public key. + * + * @param subjectKeyID identifier actual recipient will use to match the private key. + * @param publicKey the public key for encrypting the secret key. + * @return the current instance. + * @throws CertificateEncodingException + */ + public JceKeyAgreeRecipientInfoGenerator addRecipient(byte[] subjectKeyID, PublicKey publicKey) + throws CertificateEncodingException + { + recipientIDs.add(new KeyAgreeRecipientIdentifier(new RecipientKeyIdentifier(subjectKeyID))); + recipientKeys.add(publicKey); + + return this; + } + + public ASN1Sequence generateRecipientEncryptedKeys(AlgorithmIdentifier keyAgreeAlgorithm, AlgorithmIdentifier keyEncryptionAlgorithm, GenericKey contentEncryptionKey) + throws CMSException + { + init(keyAgreeAlgorithm.getAlgorithm()); + + PrivateKey senderPrivateKey = this.senderPrivateKey; + + ASN1ObjectIdentifier keyAgreementOID = keyAgreeAlgorithm.getAlgorithm(); + + if (keyAgreementOID.getId().equals(CMSEnvelopedGenerator.ECMQV_SHA1KDF)) + { + senderPrivateKey = new MQVPrivateKeySpec( + senderPrivateKey, ephemeralKP.getPrivate(), ephemeralKP.getPublic()); + } + + ASN1EncodableVector recipientEncryptedKeys = new ASN1EncodableVector(); + for (int i = 0; i != recipientIDs.size(); i++) + { + PublicKey recipientPublicKey = (PublicKey)recipientKeys.get(i); + KeyAgreeRecipientIdentifier karId = (KeyAgreeRecipientIdentifier)recipientIDs.get(i); + + if (keyAgreementOID.getId().equals(CMSEnvelopedGenerator.ECMQV_SHA1KDF)) + { + recipientPublicKey = new MQVPublicKeySpec(recipientPublicKey, recipientPublicKey); + } + + try + { + // Use key agreement to choose a wrap key for this recipient + KeyAgreement keyAgreement = helper.createKeyAgreement(keyAgreementOID); + keyAgreement.init(senderPrivateKey, random); + keyAgreement.doPhase(recipientPublicKey, true); + SecretKey keyEncryptionKey = keyAgreement.generateSecret(keyEncryptionAlgorithm.getAlgorithm().getId()); + + // Wrap the content encryption key with the agreement key + Cipher keyEncryptionCipher = helper.createCipher(keyEncryptionAlgorithm.getAlgorithm()); + + keyEncryptionCipher.init(Cipher.WRAP_MODE, keyEncryptionKey, random); + + byte[] encryptedKeyBytes = keyEncryptionCipher.wrap(helper.getJceKey(contentEncryptionKey)); + + ASN1OctetString encryptedKey = new DEROctetString(encryptedKeyBytes); + + recipientEncryptedKeys.add(new RecipientEncryptedKey(karId, encryptedKey)); + } + catch (GeneralSecurityException e) + { + throw new CMSException("cannot perform agreement step: " + e.getMessage(), e); + } + } + + return new DERSequence(recipientEncryptedKeys); + } + + protected ASN1Encodable getUserKeyingMaterial(AlgorithmIdentifier keyAgreeAlg) + throws CMSException + { + init(keyAgreeAlg.getAlgorithm()); + + if (ephemeralKP != null) + { + return new MQVuserKeyingMaterial( + createOriginatorPublicKey(SubjectPublicKeyInfo.getInstance(ephemeralKP.getPublic().getEncoded())), null); + } + + return null; + } + + private void init(ASN1ObjectIdentifier keyAgreementOID) + throws CMSException + { + if (random == null) + { + random = new SecureRandom(); + } + + if (keyAgreementOID.equals(CMSAlgorithm.ECMQV_SHA1KDF)) + { + if (ephemeralKP == null) + { + try + { + ECParameterSpec ecParamSpec = ((ECPublicKey)senderPublicKey).getParams(); + + KeyPairGenerator ephemKPG = helper.createKeyPairGenerator(keyAgreementOID); + + ephemKPG.initialize(ecParamSpec, random); + + ephemeralKP = ephemKPG.generateKeyPair(); + } + catch (InvalidAlgorithmParameterException e) + { + throw new CMSException( + "cannot determine MQV ephemeral key pair parameters from public key: " + e); + } + } + } + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKeyTransAuthenticatedRecipient.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKeyTransAuthenticatedRecipient.java new file mode 100644 index 000000000..31b383033 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKeyTransAuthenticatedRecipient.java @@ -0,0 +1,60 @@ +package org.spongycastle.cms.jcajce; + +import java.io.OutputStream; +import java.security.Key; +import java.security.PrivateKey; + +import javax.crypto.Mac; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cms.CMSException; +import org.spongycastle.cms.RecipientOperator; +import org.spongycastle.jcajce.io.MacOutputStream; +import org.spongycastle.operator.GenericKey; +import org.spongycastle.operator.MacCalculator; + + +/** + * the KeyTransRecipientInformation class for a recipient who has been sent a secret + * key encrypted using their public key that needs to be used to + * extract the message. + */ +public class JceKeyTransAuthenticatedRecipient + extends JceKeyTransRecipient +{ + public JceKeyTransAuthenticatedRecipient(PrivateKey recipientKey) + { + super(recipientKey); + } + + public RecipientOperator getRecipientOperator(AlgorithmIdentifier keyEncryptionAlgorithm, final AlgorithmIdentifier contentMacAlgorithm, byte[] encryptedContentEncryptionKey) + throws CMSException + { + final Key secretKey = extractSecretKey(keyEncryptionAlgorithm, contentMacAlgorithm, encryptedContentEncryptionKey); + + final Mac dataMac = contentHelper.createContentMac(secretKey, contentMacAlgorithm); + + return new RecipientOperator(new MacCalculator() + { + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return contentMacAlgorithm; + } + + public GenericKey getKey() + { + return new GenericKey(secretKey); + } + + public OutputStream getOutputStream() + { + return new MacOutputStream(dataMac); + } + + public byte[] getMac() + { + return dataMac.doFinal(); + } + }); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKeyTransEnvelopedRecipient.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKeyTransEnvelopedRecipient.java new file mode 100644 index 000000000..8ccc6bf71 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKeyTransEnvelopedRecipient.java @@ -0,0 +1,43 @@ +package org.spongycastle.cms.jcajce; + +import java.io.InputStream; +import java.security.Key; +import java.security.PrivateKey; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cms.CMSException; +import org.spongycastle.cms.RecipientOperator; +import org.spongycastle.operator.InputDecryptor; + +public class JceKeyTransEnvelopedRecipient + extends JceKeyTransRecipient +{ + public JceKeyTransEnvelopedRecipient(PrivateKey recipientKey) + { + super(recipientKey); + } + + public RecipientOperator getRecipientOperator(AlgorithmIdentifier keyEncryptionAlgorithm, final AlgorithmIdentifier contentEncryptionAlgorithm, byte[] encryptedContentEncryptionKey) + throws CMSException + { + Key secretKey = extractSecretKey(keyEncryptionAlgorithm, contentEncryptionAlgorithm, encryptedContentEncryptionKey); + + final Cipher dataCipher = contentHelper.createContentCipher(secretKey, contentEncryptionAlgorithm); + + return new RecipientOperator(new InputDecryptor() + { + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return contentEncryptionAlgorithm; + } + + public InputStream getInputStream(InputStream dataIn) + { + return new CipherInputStream(dataIn, dataCipher); + } + }); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKeyTransRecipient.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKeyTransRecipient.java new file mode 100644 index 000000000..e3939611e --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKeyTransRecipient.java @@ -0,0 +1,156 @@ +package org.spongycastle.cms.jcajce; + +import java.security.Key; +import java.security.PrivateKey; +import java.security.Provider; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cms.CMSException; +import org.spongycastle.cms.KeyTransRecipient; +import org.spongycastle.operator.OperatorException; +import org.spongycastle.operator.jcajce.JceAsymmetricKeyUnwrapper; + +public abstract class JceKeyTransRecipient + implements KeyTransRecipient +{ + private PrivateKey recipientKey; + + protected EnvelopedDataHelper helper = new EnvelopedDataHelper(new DefaultJcaJceExtHelper()); + protected EnvelopedDataHelper contentHelper = helper; + protected Map extraMappings = new HashMap(); + protected boolean validateKeySize = false; + + public JceKeyTransRecipient(PrivateKey recipientKey) + { + this.recipientKey = recipientKey; + } + + /** + * Set the provider to use for key recovery and content processing. + * + * @param provider provider to use. + * @return this recipient. + */ + public JceKeyTransRecipient setProvider(Provider provider) + { + this.helper = new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider)); + this.contentHelper = helper; + + return this; + } + + /** + * Set the provider to use for key recovery and content processing. + * + * @param providerName the name of the provider to use. + * @return this recipient. + */ + public JceKeyTransRecipient setProvider(String providerName) + { + this.helper = new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName)); + this.contentHelper = helper; + + return this; + } + + /** + * Internally algorithm ids are converted into cipher names using a lookup table. For some providers + * the standard lookup table won't work. Use this method to establish a specific mapping from an + * algorithm identifier to a specific algorithm. + * <p> + * For example: + * <pre> + * unwrapper.setAlgorithmMapping(PKCSObjectIdentifiers.rsaEncryption, "RSA"); + * </pre> + * </p> + * @param algorithm OID of algorithm in recipient. + * @param algorithmName JCE algorithm name to use. + * @return the current Recipient. + */ + public JceKeyTransRecipient setAlgorithmMapping(ASN1ObjectIdentifier algorithm, String algorithmName) + { + extraMappings.put(algorithm, algorithmName); + + return this; + } + + /** + * Set the provider to use for content processing. If providerName is null a "no provider" search will be + * used to satisfy getInstance calls. + * + * @param provider the provider to use. + * @return this recipient. + */ + public JceKeyTransRecipient setContentProvider(Provider provider) + { + this.contentHelper = CMSUtils.createContentHelper(provider); + + return this; + } + + /** + * Set the provider to use for content processing. If providerName is null a "no provider" search will be + * used to satisfy getInstance calls. + * + * @param providerName the name of the provider to use. + * @return this recipient. + */ + public JceKeyTransRecipient setContentProvider(String providerName) + { + this.contentHelper = CMSUtils.createContentHelper(providerName); + + return this; + } + + /** + * Set validation of retrieved key sizes against the algorithm parameters for the encrypted key where possible - default is off. + * <p> + * This setting will not have any affect if the encryption algorithm in the recipient does not specify a particular key size, or + * if the unwrapper is a HSM and the byte encoding of the unwrapped secret key is not available. + * </p> + * @param doValidate true if unwrapped key's should be validated against the content encryption algorithm, false otherwise. + * @return this recipient. + */ + public JceKeyTransRecipient setKeySizeValidation(boolean doValidate) + { + this.validateKeySize = doValidate; + + return this; + } + + protected Key extractSecretKey(AlgorithmIdentifier keyEncryptionAlgorithm, AlgorithmIdentifier encryptedKeyAlgorithm, byte[] encryptedEncryptionKey) + throws CMSException + { + JceAsymmetricKeyUnwrapper unwrapper = helper.createAsymmetricUnwrapper(keyEncryptionAlgorithm, recipientKey); + + if (!extraMappings.isEmpty()) + { + for (Iterator it = extraMappings.keySet().iterator(); it.hasNext();) + { + ASN1ObjectIdentifier algorithm = (ASN1ObjectIdentifier)it.next(); + + unwrapper.setAlgorithmMapping(algorithm, (String)extraMappings.get(algorithm)); + } + } + + try + { + Key key = helper.getJceKey(encryptedKeyAlgorithm.getAlgorithm(), unwrapper.generateUnwrappedKey(encryptedKeyAlgorithm, encryptedEncryptionKey)); + + if (validateKeySize) + { + helper.keySizeCheck(encryptedKeyAlgorithm, key); + } + + return key; + } + catch (OperatorException e) + { + throw new CMSException("exception unwrapping key: " + e.getMessage(), e); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKeyTransRecipientId.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKeyTransRecipientId.java new file mode 100644 index 000000000..62ce1feaf --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKeyTransRecipientId.java @@ -0,0 +1,57 @@ +package org.spongycastle.cms.jcajce; + +import java.math.BigInteger; +import java.security.cert.X509Certificate; + +import javax.security.auth.x500.X500Principal; + +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.cms.KeyTransRecipientId; + +public class JceKeyTransRecipientId + extends KeyTransRecipientId +{ + /** + * Construct a recipient id based on the issuer, serial number and subject key identifier (if present) of the passed in + * certificate. + * + * @param certificate certificate providing the issue and serial number and subject key identifier. + */ + public JceKeyTransRecipientId(X509Certificate certificate) + { + super(convertPrincipal(certificate.getIssuerX500Principal()), certificate.getSerialNumber(), CMSUtils.getSubjectKeyId(certificate)); + } + + /** + * Construct a recipient id based on the provided issuer and serial number.. + * + * @param issuer the issuer to use. + * @param serialNumber the serial number to use. + */ + public JceKeyTransRecipientId(X500Principal issuer, BigInteger serialNumber) + { + super(convertPrincipal(issuer), serialNumber); + } + + /** + * Construct a recipient id based on the provided issuer, serial number, and subjectKeyId.. + * + * @param issuer the issuer to use. + * @param serialNumber the serial number to use. + * @param subjectKeyId the subject key ID to use. + */ + public JceKeyTransRecipientId(X500Principal issuer, BigInteger serialNumber, byte[] subjectKeyId) + { + super(convertPrincipal(issuer), serialNumber, subjectKeyId); + } + + private static X500Name convertPrincipal(X500Principal issuer) + { + if (issuer == null) + { + return null; + } + + return X500Name.getInstance(issuer.getEncoded()); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKeyTransRecipientInfoGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKeyTransRecipientInfoGenerator.java new file mode 100644 index 000000000..c3cf56939 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JceKeyTransRecipientInfoGenerator.java @@ -0,0 +1,87 @@ +package org.spongycastle.cms.jcajce; + +import java.security.Provider; +import java.security.PublicKey; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.cms.IssuerAndSerialNumber; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cert.jcajce.JcaX509CertificateHolder; +import org.spongycastle.cms.KeyTransRecipientInfoGenerator; +import org.spongycastle.operator.jcajce.JceAsymmetricKeyWrapper; + +public class JceKeyTransRecipientInfoGenerator + extends KeyTransRecipientInfoGenerator +{ + public JceKeyTransRecipientInfoGenerator(X509Certificate recipientCert) + throws CertificateEncodingException + { + super(new IssuerAndSerialNumber(new JcaX509CertificateHolder(recipientCert).toASN1Structure()), new JceAsymmetricKeyWrapper(recipientCert)); + } + + public JceKeyTransRecipientInfoGenerator(byte[] subjectKeyIdentifier, PublicKey publicKey) + { + super(subjectKeyIdentifier, new JceAsymmetricKeyWrapper(publicKey)); + } + + /** + * Create a generator overriding the algorithm type implied by the public key in the certificate passed in. + * + * @param recipientCert certificate carrying the public key. + * @param algorithmIdentifier the identifier and parameters for the encryption algorithm to be used. + */ + public JceKeyTransRecipientInfoGenerator(X509Certificate recipientCert, AlgorithmIdentifier algorithmIdentifier) + throws CertificateEncodingException + { + super(new IssuerAndSerialNumber(new JcaX509CertificateHolder(recipientCert).toASN1Structure()), new JceAsymmetricKeyWrapper(algorithmIdentifier, recipientCert.getPublicKey())); + } + + /** + * Create a generator overriding the algorithm type implied by the public key passed in. + * + * @param subjectKeyIdentifier the subject key identifier value to associate with the public key. + * @param algorithmIdentifier the identifier and parameters for the encryption algorithm to be used. + * @param publicKey the public key to use. + */ + public JceKeyTransRecipientInfoGenerator(byte[] subjectKeyIdentifier, AlgorithmIdentifier algorithmIdentifier, PublicKey publicKey) + { + super(subjectKeyIdentifier, new JceAsymmetricKeyWrapper(algorithmIdentifier, publicKey)); + } + + public JceKeyTransRecipientInfoGenerator setProvider(String providerName) + { + ((JceAsymmetricKeyWrapper)this.wrapper).setProvider(providerName); + + return this; + } + + public JceKeyTransRecipientInfoGenerator setProvider(Provider provider) + { + ((JceAsymmetricKeyWrapper)this.wrapper).setProvider(provider); + + return this; + } + + /** + * Internally algorithm ids are converted into cipher names using a lookup table. For some providers + * the standard lookup table won't work. Use this method to establish a specific mapping from an + * algorithm identifier to a specific algorithm. + * <p> + * For example: + * <pre> + * unwrapper.setAlgorithmMapping(PKCSObjectIdentifiers.rsaEncryption, "RSA"); + * </pre> + * </p> + * @param algorithm OID of algorithm in recipient. + * @param algorithmName JCE algorithm name to use. + * @return the current RecipientInfoGenerator. + */ + public JceKeyTransRecipientInfoGenerator setAlgorithmMapping(ASN1ObjectIdentifier algorithm, String algorithmName) + { + ((JceAsymmetricKeyWrapper)this.wrapper).setAlgorithmMapping(algorithm, algorithmName); + + return this; + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcePasswordAuthenticatedRecipient.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcePasswordAuthenticatedRecipient.java new file mode 100644 index 000000000..12b8c3621 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcePasswordAuthenticatedRecipient.java @@ -0,0 +1,54 @@ +package org.spongycastle.cms.jcajce; + +import java.io.OutputStream; +import java.security.Key; + +import javax.crypto.Mac; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cms.CMSException; +import org.spongycastle.cms.RecipientOperator; +import org.spongycastle.jcajce.io.MacOutputStream; +import org.spongycastle.operator.GenericKey; +import org.spongycastle.operator.MacCalculator; +import org.spongycastle.operator.jcajce.JceGenericKey; + +public class JcePasswordAuthenticatedRecipient + extends JcePasswordRecipient +{ + public JcePasswordAuthenticatedRecipient(char[] password) + { + super(password); + } + + public RecipientOperator getRecipientOperator(AlgorithmIdentifier keyEncryptionAlgorithm, final AlgorithmIdentifier contentMacAlgorithm, byte[] derivedKey, byte[] encryptedContentEncryptionKey) + throws CMSException + { + final Key secretKey = extractSecretKey(keyEncryptionAlgorithm, contentMacAlgorithm, derivedKey, encryptedContentEncryptionKey); + + final Mac dataMac = helper.createContentMac(secretKey, contentMacAlgorithm); + + return new RecipientOperator(new MacCalculator() + { + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return contentMacAlgorithm; + } + + public GenericKey getKey() + { + return new JceGenericKey(contentMacAlgorithm, secretKey); + } + + public OutputStream getOutputStream() + { + return new MacOutputStream(dataMac); + } + + public byte[] getMac() + { + return dataMac.doFinal(); + } + }); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcePasswordEnvelopedRecipient.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcePasswordEnvelopedRecipient.java new file mode 100644 index 000000000..e46c8ddb6 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcePasswordEnvelopedRecipient.java @@ -0,0 +1,42 @@ +package org.spongycastle.cms.jcajce; + +import java.io.InputStream; +import java.security.Key; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cms.CMSException; +import org.spongycastle.cms.RecipientOperator; +import org.spongycastle.operator.InputDecryptor; + +public class JcePasswordEnvelopedRecipient + extends JcePasswordRecipient +{ + public JcePasswordEnvelopedRecipient(char[] password) + { + super(password); + } + + public RecipientOperator getRecipientOperator(AlgorithmIdentifier keyEncryptionAlgorithm, final AlgorithmIdentifier contentEncryptionAlgorithm, byte[] derivedKey, byte[] encryptedContentEncryptionKey) + throws CMSException + { + Key secretKey = extractSecretKey(keyEncryptionAlgorithm, contentEncryptionAlgorithm, derivedKey, encryptedContentEncryptionKey); + + final Cipher dataCipher = helper.createContentCipher(secretKey, contentEncryptionAlgorithm); + + return new RecipientOperator(new InputDecryptor() + { + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return contentEncryptionAlgorithm; + } + + public InputStream getInputStream(InputStream dataOut) + { + return new CipherInputStream(dataOut, dataCipher); + } + }); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcePasswordRecipient.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcePasswordRecipient.java new file mode 100644 index 000000000..12f3a0673 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcePasswordRecipient.java @@ -0,0 +1,82 @@ +package org.spongycastle.cms.jcajce; + +import java.security.GeneralSecurityException; +import java.security.Key; +import java.security.Provider; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cms.CMSException; +import org.spongycastle.cms.PasswordRecipient; + +/** + * the RecipientInfo class for a recipient who has been sent a message + * encrypted using a password. + */ +public abstract class JcePasswordRecipient + implements PasswordRecipient +{ + private int schemeID = PasswordRecipient.PKCS5_SCHEME2_UTF8; + protected EnvelopedDataHelper helper = new EnvelopedDataHelper(new DefaultJcaJceExtHelper()); + private char[] password; + + JcePasswordRecipient( + char[] password) + { + this.password = password; + } + + public JcePasswordRecipient setPasswordConversionScheme(int schemeID) + { + this.schemeID = schemeID; + + return this; + } + + public JcePasswordRecipient setProvider(Provider provider) + { + this.helper = new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider)); + + return this; + } + + public JcePasswordRecipient setProvider(String providerName) + { + this.helper = new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName)); + + return this; + } + + protected Key extractSecretKey(AlgorithmIdentifier keyEncryptionAlgorithm, AlgorithmIdentifier contentEncryptionAlgorithm, byte[] derivedKey, byte[] encryptedContentEncryptionKey) + throws CMSException + { + Cipher keyEncryptionCipher = helper.createRFC3211Wrapper(keyEncryptionAlgorithm.getAlgorithm()); + + try + { + IvParameterSpec ivSpec = new IvParameterSpec(ASN1OctetString.getInstance(keyEncryptionAlgorithm.getParameters()).getOctets()); + + keyEncryptionCipher.init(Cipher.UNWRAP_MODE, new SecretKeySpec(derivedKey, keyEncryptionCipher.getAlgorithm()), ivSpec); + + return keyEncryptionCipher.unwrap(encryptedContentEncryptionKey, contentEncryptionAlgorithm.getAlgorithm().getId(), Cipher.SECRET_KEY); + } + catch (GeneralSecurityException e) + { + throw new CMSException("cannot process content encryption key: " + e.getMessage(), e); + } + } + + public int getPasswordConversionScheme() + { + return schemeID; + } + + public char[] getPassword() + { + return password; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcePasswordRecipientInfoGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcePasswordRecipientInfoGenerator.java new file mode 100644 index 000000000..e96fd804f --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/JcePasswordRecipientInfoGenerator.java @@ -0,0 +1,61 @@ +package org.spongycastle.cms.jcajce; + +import java.security.GeneralSecurityException; +import java.security.Key; +import java.security.Provider; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cms.CMSException; +import org.spongycastle.cms.PasswordRecipientInfoGenerator; +import org.spongycastle.operator.GenericKey; + +public class JcePasswordRecipientInfoGenerator + extends PasswordRecipientInfoGenerator +{ + private EnvelopedDataHelper helper = new EnvelopedDataHelper(new DefaultJcaJceExtHelper()); + + public JcePasswordRecipientInfoGenerator(ASN1ObjectIdentifier kekAlgorithm, char[] password) + { + super(kekAlgorithm, password); + } + + public JcePasswordRecipientInfoGenerator setProvider(Provider provider) + { + this.helper = new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider)); + + return this; + } + + public JcePasswordRecipientInfoGenerator setProvider(String providerName) + { + this.helper = new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName)); + + return this; + } + + public byte[] generateEncryptedBytes(AlgorithmIdentifier keyEncryptionAlgorithm, byte[] derivedKey, GenericKey contentEncryptionKey) + throws CMSException + { + Key contentEncryptionKeySpec = helper.getJceKey(contentEncryptionKey); + Cipher keyEncryptionCipher = helper.createRFC3211Wrapper(keyEncryptionAlgorithm.getAlgorithm()); + + try + { + IvParameterSpec ivSpec = new IvParameterSpec(ASN1OctetString.getInstance(keyEncryptionAlgorithm.getParameters()).getOctets()); + + keyEncryptionCipher.init(Cipher.WRAP_MODE, new SecretKeySpec(derivedKey, keyEncryptionCipher.getAlgorithm()), ivSpec); + + return keyEncryptionCipher.wrap(contentEncryptionKeySpec); + } + catch (GeneralSecurityException e) + { + throw new CMSException("cannot process content encryption key: " + e.getMessage(), e); + } + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/NamedJcaJceExtHelper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/NamedJcaJceExtHelper.java new file mode 100644 index 000000000..ce24fe092 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/NamedJcaJceExtHelper.java @@ -0,0 +1,31 @@ +package org.spongycastle.cms.jcajce; + +import java.security.PrivateKey; + +import javax.crypto.SecretKey; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.operator.SymmetricKeyUnwrapper; +import org.spongycastle.operator.jcajce.JceAsymmetricKeyUnwrapper; +import org.spongycastle.operator.jcajce.JceSymmetricKeyUnwrapper; + +class NamedJcaJceExtHelper + extends NamedJcaJceHelper + implements JcaJceExtHelper +{ + public NamedJcaJceExtHelper(String providerName) + { + super(providerName); + } + + public JceAsymmetricKeyUnwrapper createAsymmetricUnwrapper(AlgorithmIdentifier keyEncryptionAlgorithm, PrivateKey keyEncryptionKey) + { + return new JceAsymmetricKeyUnwrapper(keyEncryptionAlgorithm, keyEncryptionKey).setProvider(providerName); + } + + public SymmetricKeyUnwrapper createSymmetricUnwrapper(AlgorithmIdentifier keyEncryptionAlgorithm, SecretKey keyEncryptionKey) + { + return new JceSymmetricKeyUnwrapper(keyEncryptionAlgorithm, keyEncryptionKey).setProvider(providerName); + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/ProviderJcaJceExtHelper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/ProviderJcaJceExtHelper.java new file mode 100644 index 000000000..7bb3c37c1 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/ProviderJcaJceExtHelper.java @@ -0,0 +1,32 @@ +package org.spongycastle.cms.jcajce; + +import java.security.PrivateKey; +import java.security.Provider; + +import javax.crypto.SecretKey; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.jcajce.ProviderJcaJceHelper; +import org.spongycastle.operator.SymmetricKeyUnwrapper; +import org.spongycastle.operator.jcajce.JceAsymmetricKeyUnwrapper; +import org.spongycastle.operator.jcajce.JceSymmetricKeyUnwrapper; + +class ProviderJcaJceExtHelper + extends ProviderJcaJceHelper + implements JcaJceExtHelper +{ + public ProviderJcaJceExtHelper(Provider provider) + { + super(provider); + } + + public JceAsymmetricKeyUnwrapper createAsymmetricUnwrapper(AlgorithmIdentifier keyEncryptionAlgorithm, PrivateKey keyEncryptionKey) + { + return new JceAsymmetricKeyUnwrapper(keyEncryptionAlgorithm, keyEncryptionKey).setProvider(provider); + } + + public SymmetricKeyUnwrapper createSymmetricUnwrapper(AlgorithmIdentifier keyEncryptionAlgorithm, SecretKey keyEncryptionKey) + { + return new JceSymmetricKeyUnwrapper(keyEncryptionAlgorithm, keyEncryptionKey).setProvider(provider); + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/ZlibCompressor.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/ZlibCompressor.java new file mode 100644 index 000000000..a4787f45f --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/ZlibCompressor.java @@ -0,0 +1,24 @@ +package org.spongycastle.cms.jcajce; + +import java.io.OutputStream; +import java.util.zip.DeflaterOutputStream; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.operator.OutputCompressor; + +public class ZlibCompressor + implements OutputCompressor +{ + private static final String ZLIB = "1.2.840.113549.1.9.16.3.8"; + + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return new AlgorithmIdentifier(new ASN1ObjectIdentifier(ZLIB)); + } + + public OutputStream getOutputStream(OutputStream comOut) + { + return new DeflaterOutputStream(comOut); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/ZlibExpanderProvider.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/ZlibExpanderProvider.java new file mode 100644 index 000000000..adc012068 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/cms/jcajce/ZlibExpanderProvider.java @@ -0,0 +1,116 @@ +package org.spongycastle.cms.jcajce; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.InflaterInputStream; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.operator.InputExpander; +import org.spongycastle.operator.InputExpanderProvider; +import org.spongycastle.util.io.StreamOverflowException; + +public class ZlibExpanderProvider + implements InputExpanderProvider +{ + private final long limit; + + /** + * Base constructor. Create an expander which will not limit the size of any objects expanded in the stream. + */ + public ZlibExpanderProvider() + { + this.limit = -1; + } + + /** + * Create a provider which caps the number of expanded bytes that can be produced when the + * compressed stream is parsed. + * + * @param limit max number of bytes allowed in an expanded stream. + */ + public ZlibExpanderProvider(long limit) + { + this.limit = limit; + } + + public InputExpander get(final AlgorithmIdentifier algorithm) + { + return new InputExpander() + { + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return algorithm; + } + + public InputStream getInputStream(InputStream comIn) + { + InputStream s = new InflaterInputStream(comIn); + if (limit >= 0) + { + s = new LimitedInputStream(s, limit); + } + return s; + } + }; + } + + private static class LimitedInputStream + extends FilterInputStream + { + private long remaining; + + public LimitedInputStream(InputStream input, long limit) + { + super(input); + + this.remaining = limit; + } + + public int read() + throws IOException + { + // Only a single 'extra' byte will ever be read + if (remaining >= 0) + { + int b = super.in.read(); + if (b < 0 || --remaining >= 0) + { + return b; + } + } + + throw new StreamOverflowException("expanded byte limit exceeded"); + } + + public int read(byte[] buf, int off, int len) + throws IOException + { + if (len < 1) + { + // This will give correct exceptions/returns for strange lengths + return super.read(buf, off, len); + } + + if (remaining < 1) + { + // Will either return EOF or throw exception + read(); + return -1; + } + + /* + * Limit the underlying request to 'remaining' bytes. This ensures the + * caller will see the full 'limit' bytes before getting an exception. + * Also, only one extra byte will ever be read. + */ + int actualLen = (remaining > len ? len : (int)remaining); + int numRead = super.in.read(buf, off, actualLen); + if (numRead > 0) + { + remaining -= numRead; + } + return numRead; + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/CCPDRequestBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/CCPDRequestBuilder.java new file mode 100644 index 000000000..e48dd2a3c --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/CCPDRequestBuilder.java @@ -0,0 +1,32 @@ +package org.spongycastle.dvcs; + +import org.spongycastle.asn1.dvcs.DVCSRequestInformationBuilder; +import org.spongycastle.asn1.dvcs.Data; +import org.spongycastle.asn1.dvcs.ServiceType; + +/** + * Builder of CCPD requests (Certify Claim of Possession of Data). + */ +public class CCPDRequestBuilder + extends DVCSRequestBuilder +{ + public CCPDRequestBuilder() + { + super(new DVCSRequestInformationBuilder(ServiceType.CCPD)); + } + + /** + * Builds CCPD request. + * + * @param messageImprint - the message imprint to include. + * @return + * @throws DVCSException + */ + public DVCSRequest build(MessageImprint messageImprint) + throws DVCSException + { + Data data = new Data(messageImprint.toASN1Structure()); + + return createDVCRequest(data); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/CCPDRequestData.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/CCPDRequestData.java new file mode 100644 index 000000000..202dd774a --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/CCPDRequestData.java @@ -0,0 +1,48 @@ +package org.spongycastle.dvcs; + +import org.spongycastle.asn1.dvcs.Data; + +/** + * Data piece of DVCRequest for CCPD service (Certify Claim of Possession of Data). + * It contains CCPD-specific selector interface. + * <p/> + * This objects are constructed internally, + * to build DVCS request to CCPD service use CCPDRequestBuilder. + */ +public class CCPDRequestData + extends DVCSRequestData +{ + /** + * Construct from corresponding ASN.1 Data structure. + * Note, that data should have messageImprint choice, + * otherwise DVCSConstructionException is thrown. + * + * @param data + * @throws DVCSConstructionException + */ + CCPDRequestData(Data data) + throws DVCSConstructionException + { + super(data); + initDigest(); + } + + private void initDigest() + throws DVCSConstructionException + { + if (data.getMessageImprint() == null) + { + throw new DVCSConstructionException("DVCSRequest.data.messageImprint should be specified for CCPD service"); + } + } + + /** + * Get MessageImprint value + * + * @return + */ + public MessageImprint getMessageImprint() + { + return new MessageImprint(data.getMessageImprint()); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/CPDRequestBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/CPDRequestBuilder.java new file mode 100644 index 000000000..0b0b81890 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/CPDRequestBuilder.java @@ -0,0 +1,34 @@ +package org.spongycastle.dvcs; + +import java.io.IOException; + +import org.spongycastle.asn1.dvcs.DVCSRequestInformationBuilder; +import org.spongycastle.asn1.dvcs.Data; +import org.spongycastle.asn1.dvcs.ServiceType; + +/** + * Builder of DVCSRequests to CPD service (Certify Possession of Data). + */ +public class CPDRequestBuilder + extends DVCSRequestBuilder +{ + public CPDRequestBuilder() + { + super(new DVCSRequestInformationBuilder(ServiceType.CPD)); + } + + /** + * Build CPD request. + * + * @param messageBytes - data to be certified + * @return + * @throws DVCSException + */ + public DVCSRequest build(byte[] messageBytes) + throws DVCSException, IOException + { + Data data = new Data(messageBytes); + + return createDVCRequest(data); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/CPDRequestData.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/CPDRequestData.java new file mode 100644 index 000000000..a7c1f5a81 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/CPDRequestData.java @@ -0,0 +1,40 @@ +package org.spongycastle.dvcs; + +import org.spongycastle.asn1.dvcs.Data; + +/** + * Data piece of DVCRequest for CPD service (Certify Possession of Data). + * It contains CPD-specific selector interface. + * <p/> + * This objects are constructed internally, + * to build DVCS request to CPD service use CPDRequestBuilder. + */ +public class CPDRequestData + extends DVCSRequestData +{ + CPDRequestData(Data data) + throws DVCSConstructionException + { + super(data); + initMessage(); + } + + private void initMessage() + throws DVCSConstructionException + { + if (data.getMessage() == null) + { + throw new DVCSConstructionException("DVCSRequest.data.message should be specified for CPD service"); + } + } + + /** + * Get contained message (data to be certified). + * + * @return + */ + public byte[] getMessage() + { + return data.getMessage().getOctets(); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/DVCSConstructionException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/DVCSConstructionException.java new file mode 100644 index 000000000..73e6b0dc6 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/DVCSConstructionException.java @@ -0,0 +1,20 @@ +package org.spongycastle.dvcs; + +/** + * Exception thrown when failed to initialize some DVCS-related staff. + */ +public class DVCSConstructionException + extends DVCSException +{ + private static final long serialVersionUID = 660035299653583980L; + + public DVCSConstructionException(String message) + { + super(message); + } + + public DVCSConstructionException(String message, Throwable cause) + { + super(message, cause); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/DVCSException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/DVCSException.java new file mode 100644 index 000000000..5aa8f51e7 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/DVCSException.java @@ -0,0 +1,28 @@ +package org.spongycastle.dvcs; + +/** + * General DVCSException. + */ +public class DVCSException + extends Exception +{ + private static final long serialVersionUID = 389345256020131488L; + + private Throwable cause; + + public DVCSException(String message) + { + super(message); + } + + public DVCSException(String message, Throwable cause) + { + super(message); + this.cause = cause; + } + + public Throwable getCause() + { + return cause; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/DVCSMessage.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/DVCSMessage.java new file mode 100644 index 000000000..6758f3599 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/DVCSMessage.java @@ -0,0 +1,22 @@ +package org.spongycastle.dvcs; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.cms.ContentInfo; + +public abstract class DVCSMessage +{ + private final ContentInfo contentInfo; + + protected DVCSMessage(ContentInfo contentInfo) + { + this.contentInfo = contentInfo; + } + + public ASN1ObjectIdentifier getContentType() + { + return contentInfo.getContentType(); + } + + public abstract ASN1Encodable getContent(); +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/DVCSParsingException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/DVCSParsingException.java new file mode 100644 index 000000000..e14525768 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/DVCSParsingException.java @@ -0,0 +1,20 @@ +package org.spongycastle.dvcs; + +/** + * DVCS parsing exception - thrown when failed to parse DVCS message. + */ +public class DVCSParsingException + extends DVCSException +{ + private static final long serialVersionUID = -7895880961377691266L; + + public DVCSParsingException(String message) + { + super(message); + } + + public DVCSParsingException(String message, Throwable cause) + { + super(message, cause); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/DVCSRequest.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/DVCSRequest.java new file mode 100644 index 000000000..f8658ab54 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/DVCSRequest.java @@ -0,0 +1,134 @@ +package org.spongycastle.dvcs; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.cms.ContentInfo; +import org.spongycastle.asn1.cms.SignedData; +import org.spongycastle.asn1.dvcs.DVCSObjectIdentifiers; +import org.spongycastle.asn1.dvcs.ServiceType; +import org.spongycastle.asn1.x509.GeneralName; +import org.spongycastle.cms.CMSSignedData; + +/** + * DVCRequest is general request to DVCS (RFC 3029). + * It represents requests for all types of services. + * Requests for different services differ in DVCData structure. + */ +public class DVCSRequest + extends DVCSMessage +{ + private org.spongycastle.asn1.dvcs.DVCSRequest asn1; + private DVCSRequestInfo reqInfo; + private DVCSRequestData data; + + /** + * Constructs DVCRequest from CMS SignedData object. + * + * @param signedData the CMS SignedData object containing the request + * @throws DVCSConstructionException + */ + public DVCSRequest(CMSSignedData signedData) + throws DVCSConstructionException + { + this(SignedData.getInstance(signedData.toASN1Structure().getContent()).getEncapContentInfo()); + } + + /** + * Construct a DVCS Request from a ContentInfo + * + * @param contentInfo the contentInfo representing the DVCSRequest + * @throws DVCSConstructionException + */ + public DVCSRequest(ContentInfo contentInfo) + throws DVCSConstructionException + { + super(contentInfo); + + if (!DVCSObjectIdentifiers.id_ct_DVCSRequestData.equals(contentInfo.getContentType())) + { + throw new DVCSConstructionException("ContentInfo not a DVCS Request"); + } + + try + { + if (contentInfo.getContent().toASN1Primitive() instanceof ASN1Sequence) + { + this.asn1 = org.spongycastle.asn1.dvcs.DVCSRequest.getInstance(contentInfo.getContent()); + } + else + { + this.asn1 = org.spongycastle.asn1.dvcs.DVCSRequest.getInstance(ASN1OctetString.getInstance(contentInfo.getContent()).getOctets()); + } + } + catch (Exception e) + { + throw new DVCSConstructionException("Unable to parse content: " + e.getMessage(), e); + } + + this.reqInfo = new DVCSRequestInfo(asn1.getRequestInformation()); + + int service = reqInfo.getServiceType(); + if (service == ServiceType.CPD.getValue().intValue()) + { + this.data = new CPDRequestData(asn1.getData()); + } + else if (service == ServiceType.VSD.getValue().intValue()) + { + this.data = new VSDRequestData(asn1.getData()); + } + else if (service == ServiceType.VPKC.getValue().intValue()) + { + this.data = new VPKCRequestData(asn1.getData()); + } + else if (service == ServiceType.CCPD.getValue().intValue()) + { + this.data = new CCPDRequestData(asn1.getData()); + } + else + { + throw new DVCSConstructionException("Unknown service type: " + service); + } + } + + /** + * Return the ASN.1 DVCSRequest structure making up the body of this request. + * + * @return an org.spongycastle.asn1.dvcs.DVCSRequest object. + */ + public ASN1Encodable getContent() + { + return asn1; + } + + /** + * Get RequestInformation envelope. + * + * @return the request info object. + */ + public DVCSRequestInfo getRequestInfo() + { + return reqInfo; + } + + /** + * Get data of DVCRequest. + * Depending on type of the request it could be different subclasses of DVCRequestData. + * + * @return the request Data object. + */ + public DVCSRequestData getData() + { + return data; + } + + /** + * Get the transaction identifier of request. + * + * @return the GeneralName representing the Transaction Identifier. + */ + public GeneralName getTransactionIdentifier() + { + return asn1.getTransactionIdentifier(); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/DVCSRequestBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/DVCSRequestBuilder.java new file mode 100644 index 000000000..f1982b91f --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/DVCSRequestBuilder.java @@ -0,0 +1,131 @@ +package org.spongycastle.dvcs; + +import java.io.IOException; +import java.math.BigInteger; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.cms.ContentInfo; +import org.spongycastle.asn1.dvcs.DVCSObjectIdentifiers; +import org.spongycastle.asn1.dvcs.DVCSRequestInformationBuilder; +import org.spongycastle.asn1.dvcs.Data; +import org.spongycastle.asn1.x509.ExtensionsGenerator; +import org.spongycastle.asn1.x509.GeneralName; +import org.spongycastle.asn1.x509.GeneralNames; +import org.spongycastle.cms.CMSSignedDataGenerator; + +/** + * Common base class for client DVCRequest builders. + * This class aims at DVCSRequestInformation and TransactionIdentifier construction, + * and its subclasses - for Data field construction (as it is specific for the requested service). + */ +public abstract class DVCSRequestBuilder +{ + private final ExtensionsGenerator extGenerator = new ExtensionsGenerator(); + private final CMSSignedDataGenerator signedDataGen = new CMSSignedDataGenerator(); + + protected final DVCSRequestInformationBuilder requestInformationBuilder; + + protected DVCSRequestBuilder(DVCSRequestInformationBuilder requestInformationBuilder) + { + this.requestInformationBuilder = requestInformationBuilder; + } + + /** + * Set a nonce for this request, + * + * @param nonce + */ + public void setNonce(BigInteger nonce) + { + requestInformationBuilder.setNonce(nonce); + } + + /** + * Set requester name. + * + * @param requester + */ + public void setRequester(GeneralName requester) + { + requestInformationBuilder.setRequester(requester); + } + + /** + * Set DVCS name to generated requests. + * + * @param dvcs + */ + public void setDVCS(GeneralName dvcs) + { + requestInformationBuilder.setDVCS(dvcs); + } + + /** + * Set DVCS name to generated requests. + * + * @param dvcs + */ + public void setDVCS(GeneralNames dvcs) + { + requestInformationBuilder.setDVCS(dvcs); + } + + /** + * Set data location to generated requests. + * + * @param dataLocation + */ + public void setDataLocations(GeneralName dataLocation) + { + requestInformationBuilder.setDataLocations(dataLocation); + } + + /** + * Set data location to generated requests. + * + * @param dataLocations + */ + public void setDataLocations(GeneralNames dataLocations) + { + requestInformationBuilder.setDataLocations(dataLocations); + } + + /** + * Add a given extension field. + * + * @param oid the OID defining the extension type. + * @param isCritical true if the extension is critical, false otherwise. + * @param value the ASN.1 structure that forms the extension's value. + * @return this builder object. + * @throws DVCSException if there is an issue encoding the extension for adding. + */ + public void addExtension( + ASN1ObjectIdentifier oid, + boolean isCritical, + ASN1Encodable value) + throws DVCSException + { + try + { + extGenerator.addExtension(oid, isCritical, value); + } + catch (IOException e) + { + throw new DVCSException("cannot encode extension: " + e.getMessage(), e); + } + } + + protected DVCSRequest createDVCRequest(Data data) + throws DVCSException + { + if (!extGenerator.isEmpty()) + { + requestInformationBuilder.setExtensions(extGenerator.generate()); + } + + org.spongycastle.asn1.dvcs.DVCSRequest request = new org.spongycastle.asn1.dvcs.DVCSRequest(requestInformationBuilder.build(), data); + + return new DVCSRequest(new ContentInfo(DVCSObjectIdentifiers.id_ct_DVCSRequestData, request)); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/DVCSRequestData.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/DVCSRequestData.java new file mode 100644 index 000000000..35cdd325e --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/DVCSRequestData.java @@ -0,0 +1,38 @@ +package org.spongycastle.dvcs; + +import org.spongycastle.asn1.dvcs.Data; + +/** + * Data piece of DVCRequest object (DVCS Data structure). + * Its contents depend on the service type. + * Its subclasses define the service-specific interface. + * <p/> + * The concrete objects of DVCRequestData are created by buildDVCRequestData static method. + */ +public abstract class DVCSRequestData +{ + /** + * The underlying data object is accessible by subclasses. + */ + protected Data data; + + /** + * The constructor is accessible by subclasses. + * + * @param data + */ + protected DVCSRequestData(Data data) + { + this.data = data; + } + + /** + * Convert to ASN.1 structure (Data). + * + * @return + */ + public Data toASN1Structure() + { + return data; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/DVCSRequestInfo.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/DVCSRequestInfo.java new file mode 100644 index 000000000..1f51e3e69 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/DVCSRequestInfo.java @@ -0,0 +1,237 @@ +package org.spongycastle.dvcs; + +import java.math.BigInteger; +import java.util.Date; + +import org.spongycastle.asn1.dvcs.DVCSRequestInformation; +import org.spongycastle.asn1.dvcs.DVCSTime; +import org.spongycastle.asn1.x509.GeneralNames; +import org.spongycastle.asn1.x509.PolicyInformation; +import org.spongycastle.tsp.TimeStampToken; +import org.spongycastle.util.Arrays; + +/** + * Information piece of DVCS requests. + * It is common for all types of DVCS requests. + */ +public class DVCSRequestInfo +{ + private DVCSRequestInformation data; + + /** + * Constructs DVCRequestInfo from byte array (DER encoded DVCSRequestInformation). + * + * @param in + */ + public DVCSRequestInfo(byte[] in) + { + this(DVCSRequestInformation.getInstance(in)); + } + + /** + * Constructs DVCRequestInfo from DVCSRequestInformation ASN.1 structure. + * + * @param data + */ + public DVCSRequestInfo(DVCSRequestInformation data) + { + this.data = data; + } + + /** + * Converts to corresponding ASN.1 structure (DVCSRequestInformation). + * + * @return + */ + public DVCSRequestInformation toASN1Structure() + { + return data; + } + + // + // DVCRequestInfo selector interface + // + + /** + * Get DVCS version of request. + * + * @return + */ + public int getVersion() + { + return data.getVersion(); + } + + /** + * Get requested service type. + * + * @return one of CPD, VSD, VPKC, CCPD (see constants). + */ + public int getServiceType() + { + return data.getService().getValue().intValue(); + } + + /** + * Get nonce if it is set. + * Note: this field can be set (if not present) or extended (if present) by DVCS. + * + * @return nonce value, or null if it is not set. + */ + public BigInteger getNonce() + { + return data.getNonce(); + } + + /** + * Get request generation time if it is set. + * + * @return time of request, or null if it is not set. + * @throws DVCSParsingException if a request time is present but cannot be extracted. + */ + public Date getRequestTime() + throws DVCSParsingException + { + DVCSTime time = data.getRequestTime(); + + if (time == null) + { + return null; + } + + try + { + if (time.getGenTime() != null) + { + return time.getGenTime().getDate(); + } + else + { + TimeStampToken token = new TimeStampToken(time.getTimeStampToken()); + + return token.getTimeStampInfo().getGenTime(); + } + } + catch (Exception e) + { + throw new DVCSParsingException("unable to extract time: " + e.getMessage(), e); + } + } + + /** + * Get names of requesting entity, if set. + * + * @return + */ + public GeneralNames getRequester() + { + return data.getRequester(); + } + + /** + * Get policy, under which the validation is requested. + * + * @return policy identifier or null, if any policy is acceptable. + */ + public PolicyInformation getRequestPolicy() + { + if (data.getRequestPolicy() != null) + { + return data.getRequestPolicy(); + } + return null; + } + + /** + * Get names of DVCS servers. + * Note: this field can be set by DVCS. + * + * @return + */ + public GeneralNames getDVCSNames() + { + return data.getDVCS(); + } + + /** + * Get data locations, where the copy of request Data can be obtained. + * Note: the exact meaning of field is up to applications. + * Note: this field can be set by DVCS. + * + * @return + */ + public GeneralNames getDataLocations() + { + return data.getDataLocations(); + } + + /** + * Compares two DVCRequestInfo structures: one from DVCRequest, and one from DVCResponse. + * This function implements RFC 3029, 9.1 checks of reqInfo. + * + * @param requestInfo - DVCRequestInfo of DVCRequest + * @param responseInfo - DVCRequestInfo of DVCResponse + * @return true if server's requestInfo matches client's requestInfo + */ + public static boolean validate(DVCSRequestInfo requestInfo, DVCSRequestInfo responseInfo) + { + // RFC 3029, 9.1 + // The DVCS MAY modify the fields: + // 'dvcs', 'requester', 'dataLocations', and 'nonce' of the ReqInfo structure. + + DVCSRequestInformation clientInfo = requestInfo.data; + DVCSRequestInformation serverInfo = responseInfo.data; + + if (clientInfo.getVersion() != serverInfo.getVersion()) + { + return false; + } + if (!clientEqualsServer(clientInfo.getService(), serverInfo.getService())) + { + return false; + } + if (!clientEqualsServer(clientInfo.getRequestTime(), serverInfo.getRequestTime())) + { + return false; + } + if (!clientEqualsServer(clientInfo.getRequestPolicy(), serverInfo.getRequestPolicy())) + { + return false; + } + if (!clientEqualsServer(clientInfo.getExtensions(), serverInfo.getExtensions())) + { + return false; + } + + // RFC 3029, 9.1. The only modification allowed to a 'nonce' + // is the inclusion of a new field if it was not present, + // or to concatenate other data to the end (right) of an existing value. + + if (clientInfo.getNonce() != null) + { + if (serverInfo.getNonce() == null) + { + return false; + } + byte[] clientNonce = clientInfo.getNonce().toByteArray(); + byte[] serverNonce = serverInfo.getNonce().toByteArray(); + if (serverNonce.length < clientNonce.length) + { + return false; + } + if (!Arrays.areEqual(clientNonce, Arrays.copyOfRange(serverNonce, 0, clientNonce.length))) + { + return false; + } + } + + return true; + } + + // null-protected compare of any two objects + private static boolean clientEqualsServer(Object client, Object server) + { + return (client == null && server == null) || (client != null && client.equals(server)); + } +} + diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/DVCSResponse.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/DVCSResponse.java new file mode 100644 index 000000000..f71855195 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/DVCSResponse.java @@ -0,0 +1,74 @@ +package org.spongycastle.dvcs; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.cms.ContentInfo; +import org.spongycastle.asn1.cms.SignedData; +import org.spongycastle.asn1.dvcs.DVCSObjectIdentifiers; +import org.spongycastle.cms.CMSSignedData; + +/** + * DVCResponse is general response to DVCS (RFC 3029). + * It represents responses for all types of services. + */ +public class DVCSResponse + extends DVCSMessage +{ + private org.spongycastle.asn1.dvcs.DVCSResponse asn1; + + /** + * Constructs DVCRequest from CMS SignedData object. + * + * @param signedData the CMS SignedData object containing the request + * @throws org.spongycastle.dvcs.DVCSConstructionException + */ + public DVCSResponse(CMSSignedData signedData) + throws DVCSConstructionException + { + this(SignedData.getInstance(signedData.toASN1Structure().getContent()).getEncapContentInfo()); + } + + /** + * Construct a DVCS Request from a ContentInfo + * + * @param contentInfo the contentInfo representing the DVCSRequest + * @throws org.spongycastle.dvcs.DVCSConstructionException + */ + public DVCSResponse(ContentInfo contentInfo) + throws DVCSConstructionException + { + super(contentInfo); + + if (!DVCSObjectIdentifiers.id_ct_DVCSResponseData.equals(contentInfo.getContentType())) + { + throw new DVCSConstructionException("ContentInfo not a DVCS Request"); + } + + try + { + if (contentInfo.getContent().toASN1Primitive() instanceof ASN1Sequence) + { + this.asn1 = org.spongycastle.asn1.dvcs.DVCSResponse.getInstance(contentInfo.getContent()); + } + else + { + this.asn1 = org.spongycastle.asn1.dvcs.DVCSResponse.getInstance(ASN1OctetString.getInstance(contentInfo.getContent()).getOctets()); + } + } + catch (Exception e) + { + throw new DVCSConstructionException("Unable to parse content: " + e.getMessage(), e); + } + } + + /** + * Return the ASN.1 DVCSResponse structure making up the body of this response. + * + * @return an org.spongycastle.asn1.dvcs.DVCSResponse object. + */ + public ASN1Encodable getContent() + { + return asn1; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/MessageImprint.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/MessageImprint.java new file mode 100644 index 000000000..c9793880b --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/MessageImprint.java @@ -0,0 +1,38 @@ +package org.spongycastle.dvcs; + +import org.spongycastle.asn1.x509.DigestInfo; + +public class MessageImprint +{ + private final DigestInfo messageImprint; + + public MessageImprint(DigestInfo messageImprint) + { + this.messageImprint = messageImprint; + } + + public DigestInfo toASN1Structure() + { + return messageImprint; + } + + public boolean equals(Object o) + { + if (o == this) + { + return true; + } + + if (o instanceof MessageImprint) + { + return messageImprint.equals(((MessageImprint)o).messageImprint); + } + + return false; + } + + public int hashCode() + { + return messageImprint.hashCode(); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/MessageImprintBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/MessageImprintBuilder.java new file mode 100644 index 000000000..f7dc987e0 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/MessageImprintBuilder.java @@ -0,0 +1,35 @@ +package org.spongycastle.dvcs; + +import java.io.OutputStream; + +import org.spongycastle.asn1.x509.DigestInfo; +import org.spongycastle.operator.DigestCalculator; + +public class MessageImprintBuilder +{ + private final DigestCalculator digestCalculator; + + public MessageImprintBuilder(DigestCalculator digestCalculator) + { + this.digestCalculator = digestCalculator; + } + + public MessageImprint build(byte[] message) + throws DVCSException + { + try + { + OutputStream dOut = digestCalculator.getOutputStream(); + + dOut.write(message); + + dOut.close(); + + return new MessageImprint(new DigestInfo(digestCalculator.getAlgorithmIdentifier(), digestCalculator.getDigest())); + } + catch (Exception e) + { + throw new DVCSException("unable to build MessageImprint: " + e.getMessage(), e); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/SignedDVCSMessageGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/SignedDVCSMessageGenerator.java new file mode 100644 index 000000000..2378bcf2d --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/SignedDVCSMessageGenerator.java @@ -0,0 +1,45 @@ +package org.spongycastle.dvcs; + +import java.io.IOException; + +import org.spongycastle.asn1.ASN1Encoding; +import org.spongycastle.cms.CMSException; +import org.spongycastle.cms.CMSProcessableByteArray; +import org.spongycastle.cms.CMSSignedData; +import org.spongycastle.cms.CMSSignedDataGenerator; + +public class SignedDVCSMessageGenerator +{ + private final CMSSignedDataGenerator signedDataGen; + + public SignedDVCSMessageGenerator(CMSSignedDataGenerator signedDataGen) + { + this.signedDataGen = signedDataGen; + } + + /** + * Creates a CMSSignedData object containing the passed in DVCSMessage + * + * @param message the request to be signed. + * @return an encapsulating SignedData object. + * @throws DVCSException in the event of failure to encode the request or sign it. + */ + public CMSSignedData build(DVCSMessage message) + throws DVCSException + { + try + { + byte[] encapsulatedData = message.getContent().toASN1Primitive().getEncoded(ASN1Encoding.DER); + + return signedDataGen.generate(new CMSProcessableByteArray(message.getContentType(), encapsulatedData), true); + } + catch (CMSException e) + { + throw new DVCSException("Could not sign DVCS request", e); + } + catch (IOException e) + { + throw new DVCSException("Could not encode DVCS request", e); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/TargetChain.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/TargetChain.java new file mode 100644 index 000000000..d45714f32 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/TargetChain.java @@ -0,0 +1,18 @@ +package org.spongycastle.dvcs; + +import org.spongycastle.asn1.dvcs.TargetEtcChain; + +public class TargetChain +{ + private final TargetEtcChain certs; + + public TargetChain(TargetEtcChain certs) + { + this.certs = certs; + } + + public TargetEtcChain toASN1Structure() + { + return certs; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/VPKCRequestBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/VPKCRequestBuilder.java new file mode 100644 index 000000000..9a68b7d3b --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/VPKCRequestBuilder.java @@ -0,0 +1,76 @@ +package org.spongycastle.dvcs; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.spongycastle.asn1.dvcs.CertEtcToken; +import org.spongycastle.asn1.dvcs.DVCSRequestInformationBuilder; +import org.spongycastle.asn1.dvcs.DVCSTime; +import org.spongycastle.asn1.dvcs.Data; +import org.spongycastle.asn1.dvcs.ServiceType; +import org.spongycastle.asn1.dvcs.TargetEtcChain; +import org.spongycastle.asn1.x509.Extension; +import org.spongycastle.cert.X509CertificateHolder; + +/** + * Builder of DVC requests to VPKC service (Verify Public Key Certificates). + */ +public class VPKCRequestBuilder + extends DVCSRequestBuilder +{ + private List chains = new ArrayList(); + + public VPKCRequestBuilder() + { + super(new DVCSRequestInformationBuilder(ServiceType.VPKC)); + } + + /** + * Adds a TargetChain representing a X.509 certificate to the request. + * + * @param cert the certificate to be added + */ + public void addTargetChain(X509CertificateHolder cert) + { + chains.add(new TargetEtcChain(new CertEtcToken(CertEtcToken.TAG_CERTIFICATE, cert.toASN1Structure()))); + } + + /** + * Adds a TargetChain representing a single X.509 Extension to the request + * + * @param extension the extension to be added. + */ + public void addTargetChain(Extension extension) + { + chains.add(new TargetEtcChain(new CertEtcToken(extension))); + } + + /** + * Adds a X.509 certificate to the request. + * + * @param targetChain the CertChain object to be added. + */ + public void addTargetChain(TargetChain targetChain) + { + chains.add(targetChain.toASN1Structure()); + } + + public void setRequestTime(Date requestTime) + { + requestInformationBuilder.setRequestTime(new DVCSTime(requestTime)); + } + + /** + * Build DVCS request to VPKC service. + * + * @throws DVCSException + */ + public DVCSRequest build() + throws DVCSException + { + Data data = new Data((TargetEtcChain[])chains.toArray(new TargetEtcChain[chains.size()])); + + return createDVCRequest(data); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/VPKCRequestData.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/VPKCRequestData.java new file mode 100644 index 000000000..561f93bfd --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/VPKCRequestData.java @@ -0,0 +1,51 @@ +package org.spongycastle.dvcs; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.spongycastle.asn1.dvcs.Data; +import org.spongycastle.asn1.dvcs.TargetEtcChain; + +/** + * Data piece of DVCS request to VPKC service (Verify Public Key Certificates). + * It contains VPKC-specific interface. + * <p/> + * This objects are constructed internally, + * to build DVCS request to VPKC service use VPKCRequestBuilder. + */ +public class VPKCRequestData + extends DVCSRequestData +{ + private List chains; + + VPKCRequestData(Data data) + throws DVCSConstructionException + { + super(data); + + TargetEtcChain[] certs = data.getCerts(); + + if (certs == null) + { + throw new DVCSConstructionException("DVCSRequest.data.certs should be specified for VPKC service"); + } + + chains = new ArrayList(certs.length); + + for (int i = 0; i != certs.length; i++) + { + chains.add(new TargetChain(certs[i])); + } + } + + /** + * Get contained certs choice data.. + * + * @return a list of CertChain objects. + */ + public List getCerts() + { + return Collections.unmodifiableList(chains); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/VSDRequestBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/VSDRequestBuilder.java new file mode 100644 index 000000000..f2f7df54e --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/VSDRequestBuilder.java @@ -0,0 +1,49 @@ +package org.spongycastle.dvcs; + +import java.io.IOException; +import java.util.Date; + +import org.spongycastle.asn1.dvcs.DVCSRequestInformationBuilder; +import org.spongycastle.asn1.dvcs.DVCSTime; +import org.spongycastle.asn1.dvcs.Data; +import org.spongycastle.asn1.dvcs.ServiceType; +import org.spongycastle.cms.CMSSignedData; + +/** + * Builder of DVCS requests to VSD service (Verify Signed Document). + */ +public class VSDRequestBuilder + extends DVCSRequestBuilder +{ + public VSDRequestBuilder() + { + super(new DVCSRequestInformationBuilder(ServiceType.VSD)); + } + + public void setRequestTime(Date requestTime) + { + requestInformationBuilder.setRequestTime(new DVCSTime(requestTime)); + } + + /** + * Build VSD request from CMS SignedData object. + * + * @param document + * @return + * @throws DVCSException + */ + public DVCSRequest build(CMSSignedData document) + throws DVCSException + { + try + { + Data data = new Data(document.getEncoded()); + + return createDVCRequest(data); + } + catch (IOException e) + { + throw new DVCSException("Failed to encode CMS signed data", e); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/VSDRequestData.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/VSDRequestData.java new file mode 100644 index 000000000..21adaa3f4 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/dvcs/VSDRequestData.java @@ -0,0 +1,66 @@ +package org.spongycastle.dvcs; + +import org.spongycastle.asn1.dvcs.Data; +import org.spongycastle.cms.CMSException; +import org.spongycastle.cms.CMSSignedData; + +/** + * Data piece of DVCS request to VSD service (Verify Signed Document). + * It contains VSD-specific selector interface. + * Note: the request should contain CMS SignedData object as message. + * <p/> + * This objects are constructed internally, + * to build DVCS request to VSD service use VSDRequestBuilder. + */ +public class VSDRequestData + extends DVCSRequestData +{ + private CMSSignedData doc; + + VSDRequestData(Data data) + throws DVCSConstructionException + { + super(data); + initDocument(); + } + + private void initDocument() + throws DVCSConstructionException + { + if (doc == null) + { + if (data.getMessage() == null) + { + throw new DVCSConstructionException("DVCSRequest.data.message should be specified for VSD service"); + } + try + { + doc = new CMSSignedData(data.getMessage().getOctets()); + } + catch (CMSException e) + { + throw new DVCSConstructionException("Can't read CMS SignedData from input", e); + } + } + } + + /** + * Get contained message (data to be certified). + * + * @return + */ + public byte[] getMessage() + { + return data.getMessage().getOctets(); + } + + /** + * Get the CMS SignedData object represented by the encoded message. + * + * @return + */ + public CMSSignedData getParsedMessage() + { + return doc; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/EACCertificateBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/EACCertificateBuilder.java new file mode 100644 index 000000000..b3ca48e8c --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/EACCertificateBuilder.java @@ -0,0 +1,83 @@ +package org.spongycastle.eac; + +import java.io.OutputStream; + +import org.spongycastle.asn1.ASN1Encoding; +import org.spongycastle.asn1.DERApplicationSpecific; +import org.spongycastle.asn1.eac.CVCertificate; +import org.spongycastle.asn1.eac.CertificateBody; +import org.spongycastle.asn1.eac.CertificateHolderAuthorization; +import org.spongycastle.asn1.eac.CertificateHolderReference; +import org.spongycastle.asn1.eac.CertificationAuthorityReference; +import org.spongycastle.asn1.eac.EACTags; +import org.spongycastle.asn1.eac.PackedDate; +import org.spongycastle.asn1.eac.PublicKeyDataObject; +import org.spongycastle.eac.operator.EACSigner; + +public class EACCertificateBuilder +{ + private static final byte [] ZeroArray = new byte [] {0}; + + private PublicKeyDataObject publicKey; + private CertificateHolderAuthorization certificateHolderAuthorization; + private PackedDate certificateEffectiveDate; + private PackedDate certificateExpirationDate; + private CertificateHolderReference certificateHolderReference; + private CertificationAuthorityReference certificationAuthorityReference; + + public EACCertificateBuilder( + CertificationAuthorityReference certificationAuthorityReference, + PublicKeyDataObject publicKey, + CertificateHolderReference certificateHolderReference, + CertificateHolderAuthorization certificateHolderAuthorization, + PackedDate certificateEffectiveDate, + PackedDate certificateExpirationDate) + { + this.certificationAuthorityReference = certificationAuthorityReference; + this.publicKey = publicKey; + this.certificateHolderReference = certificateHolderReference; + this.certificateHolderAuthorization = certificateHolderAuthorization; + this.certificateEffectiveDate = certificateEffectiveDate; + this.certificateExpirationDate = certificateExpirationDate; + } + + private CertificateBody buildBody() + { + DERApplicationSpecific certificateProfileIdentifier; + + certificateProfileIdentifier = new DERApplicationSpecific( + EACTags.INTERCHANGE_PROFILE, ZeroArray); + + CertificateBody body = new CertificateBody( + certificateProfileIdentifier, + certificationAuthorityReference, + publicKey, + certificateHolderReference, + certificateHolderAuthorization, + certificateEffectiveDate, + certificateExpirationDate); + + return body; + } + + public EACCertificateHolder build(EACSigner signer) + throws EACException + { + try + { + CertificateBody body = buildBody(); + + OutputStream vOut = signer.getOutputStream(); + + vOut.write(body.getEncoded(ASN1Encoding.DER)); + + vOut.close(); + + return new EACCertificateHolder(new CVCertificate(body, signer.getSignature())); + } + catch (Exception e) + { + throw new EACException("unable to process signature: " + e.getMessage(), e); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/EACCertificateHolder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/EACCertificateHolder.java new file mode 100644 index 000000000..edc75a6d3 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/EACCertificateHolder.java @@ -0,0 +1,88 @@ +package org.spongycastle.eac; + +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.asn1.ASN1Encoding; +import org.spongycastle.asn1.ASN1ParsingException; +import org.spongycastle.asn1.eac.CVCertificate; +import org.spongycastle.asn1.eac.PublicKeyDataObject; +import org.spongycastle.eac.operator.EACSignatureVerifier; + +public class EACCertificateHolder +{ + private CVCertificate cvCertificate; + + private static CVCertificate parseBytes(byte[] certEncoding) + throws IOException + { + try + { + return CVCertificate.getInstance(certEncoding); + } + catch (ClassCastException e) + { + throw new EACIOException("malformed data: " + e.getMessage(), e); + } + catch (IllegalArgumentException e) + { + throw new EACIOException("malformed data: " + e.getMessage(), e); + } + catch (ASN1ParsingException e) + { + if (e.getCause() instanceof IOException) + { + throw (IOException)e.getCause(); + } + else + { + throw new EACIOException("malformed data: " + e.getMessage(), e); + } + } + } + + public EACCertificateHolder(byte[] certEncoding) + throws IOException + { + this(parseBytes(certEncoding)); + } + + public EACCertificateHolder(CVCertificate cvCertificate) + { + this.cvCertificate = cvCertificate; + } + + /** + * Return the underlying ASN.1 structure for the certificate in this holder. + * + * @return a X509CertificateStructure object. + */ + public CVCertificate toASN1Structure() + { + return cvCertificate; + } + + public PublicKeyDataObject getPublicKeyDataObject() + { + return cvCertificate.getBody().getPublicKey(); + } + + public boolean isSignatureValid(EACSignatureVerifier verifier) + throws EACException + { + try + { + OutputStream vOut = verifier.getOutputStream(); + + vOut.write(cvCertificate.getBody().getEncoded(ASN1Encoding.DER)); + + vOut.close(); + + return verifier.verify(cvCertificate.getSignature()); + } + catch (Exception e) + { + throw new EACException("unable to process signature: " + e.getMessage(), e); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/EACCertificateRequestHolder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/EACCertificateRequestHolder.java new file mode 100644 index 000000000..77b01e4d1 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/EACCertificateRequestHolder.java @@ -0,0 +1,88 @@ +package org.spongycastle.eac; + +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.asn1.ASN1Encoding; +import org.spongycastle.asn1.ASN1ParsingException; +import org.spongycastle.asn1.eac.CVCertificateRequest; +import org.spongycastle.asn1.eac.PublicKeyDataObject; +import org.spongycastle.eac.operator.EACSignatureVerifier; + +public class EACCertificateRequestHolder +{ + private CVCertificateRequest request; + + private static CVCertificateRequest parseBytes(byte[] requestEncoding) + throws IOException + { + try + { + return CVCertificateRequest.getInstance(requestEncoding); + } + catch (ClassCastException e) + { + throw new EACIOException("malformed data: " + e.getMessage(), e); + } + catch (IllegalArgumentException e) + { + throw new EACIOException("malformed data: " + e.getMessage(), e); + } + catch (ASN1ParsingException e) + { + if (e.getCause() instanceof IOException) + { + throw (IOException)e.getCause(); + } + else + { + throw new EACIOException("malformed data: " + e.getMessage(), e); + } + } + } + + public EACCertificateRequestHolder(byte[] certEncoding) + throws IOException + { + this(parseBytes(certEncoding)); + } + + public EACCertificateRequestHolder(CVCertificateRequest request) + { + this.request = request; + } + + /** + * Return the underlying ASN.1 structure for the certificate in this holder. + * + * @return a X509CertificateStructure object. + */ + public CVCertificateRequest toASN1Structure() + { + return request; + } + + public PublicKeyDataObject getPublicKeyDataObject() + { + return request.getPublicKey(); + } + + public boolean isInnerSignatureValid(EACSignatureVerifier verifier) + throws EACException + { + try + { + OutputStream vOut = verifier.getOutputStream(); + + vOut.write(request.getCertificateBody().getEncoded(ASN1Encoding.DER)); + + vOut.close(); + + return verifier.verify(request.getInnerSignature()); + } + catch (Exception e) + { + throw new EACException("unable to process signature: " + e.getMessage(), e); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/EACException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/EACException.java new file mode 100644 index 000000000..d8e1612a8 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/EACException.java @@ -0,0 +1,27 @@ +package org.spongycastle.eac; + +/** + * General checked Exception thrown in the cert package and its sub-packages. + */ +public class EACException + extends Exception +{ + private Throwable cause; + + public EACException(String msg, Throwable cause) + { + super(msg); + + this.cause = cause; + } + + public EACException(String msg) + { + super(msg); + } + + public Throwable getCause() + { + return cause; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/EACIOException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/EACIOException.java new file mode 100644 index 000000000..857a6f6a7 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/EACIOException.java @@ -0,0 +1,29 @@ +package org.spongycastle.eac; + +import java.io.IOException; + +/** + * General IOException thrown in the cert package and its sub-packages. + */ +public class EACIOException + extends IOException +{ + private Throwable cause; + + public EACIOException(String msg, Throwable cause) + { + super(msg); + + this.cause = cause; + } + + public EACIOException(String msg) + { + super(msg); + } + + public Throwable getCause() + { + return cause; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/jcajce/DefaultEACHelper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/jcajce/DefaultEACHelper.java new file mode 100644 index 000000000..703e1405a --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/jcajce/DefaultEACHelper.java @@ -0,0 +1,14 @@ +package org.spongycastle.eac.jcajce; + +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; + +class DefaultEACHelper + implements EACHelper +{ + public KeyFactory createKeyFactory(String type) + throws NoSuchAlgorithmException + { + return KeyFactory.getInstance(type); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/jcajce/EACHelper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/jcajce/EACHelper.java new file mode 100644 index 000000000..c6f3ffb27 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/jcajce/EACHelper.java @@ -0,0 +1,11 @@ +package org.spongycastle.eac.jcajce; + +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; + +interface EACHelper +{ + KeyFactory createKeyFactory(String type) + throws NoSuchProviderException, NoSuchAlgorithmException; +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/jcajce/JcaPublicKeyConverter.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/jcajce/JcaPublicKeyConverter.java new file mode 100644 index 000000000..9e6a7ecae --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/jcajce/JcaPublicKeyConverter.java @@ -0,0 +1,168 @@ +package org.spongycastle.eac.jcajce; + +import java.math.BigInteger; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Provider; +import java.security.PublicKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECField; +import java.security.spec.ECFieldFp; +import java.security.spec.EllipticCurve; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.RSAPublicKeySpec; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.eac.EACObjectIdentifiers; +import org.spongycastle.asn1.eac.ECDSAPublicKey; +import org.spongycastle.asn1.eac.PublicKeyDataObject; +import org.spongycastle.asn1.eac.RSAPublicKey; +import org.spongycastle.eac.EACException; +import org.spongycastle.jce.spec.ECParameterSpec; +import org.spongycastle.jce.spec.ECPublicKeySpec; +import org.spongycastle.math.ec.ECCurve; +import org.spongycastle.math.ec.ECPoint; + +public class JcaPublicKeyConverter +{ + private EACHelper helper = new DefaultEACHelper(); + + public JcaPublicKeyConverter setProvider(String providerName) + { + this.helper = new NamedEACHelper(providerName); + + return this; + } + + public JcaPublicKeyConverter setProvider(Provider provider) + { + this.helper = new ProviderEACHelper(provider); + + return this; + } + + public PublicKey getKey(PublicKeyDataObject publicKeyDataObject) + throws EACException, InvalidKeySpecException + { + if (publicKeyDataObject.getUsage().on(EACObjectIdentifiers.id_TA_ECDSA)) + { + return getECPublicKeyPublicKey((ECDSAPublicKey)publicKeyDataObject); + } + else + { + RSAPublicKey pubKey = (RSAPublicKey)publicKeyDataObject; + RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(pubKey.getModulus(), pubKey.getPublicExponent()); + + try + { + KeyFactory factk = helper.createKeyFactory("RSA"); + + return factk.generatePublic(pubKeySpec); + } + catch (NoSuchProviderException e) + { + throw new EACException("cannot find provider: " + e.getMessage(), e); + } + catch (NoSuchAlgorithmException e) + { + throw new EACException("cannot find algorithm ECDSA: " + e.getMessage(), e); + } + } + } + + private PublicKey getECPublicKeyPublicKey(ECDSAPublicKey key) + throws EACException, InvalidKeySpecException + { + ECParameterSpec spec = getParams(key); + ECCurve curve = spec.getCurve(); + + ECPoint point = curve.decodePoint(key.getPublicPointY()); + ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, spec); + + KeyFactory factk; + try + { + factk = helper.createKeyFactory("ECDSA"); + } + catch (NoSuchProviderException e) + { + throw new EACException("cannot find provider: " + e.getMessage(), e); + } + catch (NoSuchAlgorithmException e) + { + throw new EACException("cannot find algorithm ECDSA: " + e.getMessage(), e); + } + + return factk.generatePublic(pubKeySpec); + } + + private ECParameterSpec getParams(ECDSAPublicKey key) + { + if (!key.hasParameters()) + { + throw new IllegalArgumentException("Public key does not contains EC Params"); + } + + BigInteger p = key.getPrimeModulusP(); + ECCurve.Fp curve = new ECCurve.Fp(p, key.getFirstCoefA(), key.getSecondCoefB()); + + ECPoint G = curve.decodePoint(key.getBasePointG()); + + BigInteger order = key.getOrderOfBasePointR(); + BigInteger coFactor = key.getCofactorF(); + // TODO: update to use JDK 1.5 EC API + ECParameterSpec ecspec = new ECParameterSpec(curve, G, order, coFactor); + + return ecspec; + } + + public PublicKeyDataObject getPublicKeyDataObject(ASN1ObjectIdentifier usage, PublicKey publicKey) + { + if (publicKey instanceof java.security.interfaces.RSAPublicKey) + { + java.security.interfaces.RSAPublicKey pubKey = (java.security.interfaces.RSAPublicKey)publicKey; + + return new RSAPublicKey(usage, pubKey.getModulus(), pubKey.getPublicExponent()); + } + else + { + ECPublicKey pubKey = (ECPublicKey)publicKey; + java.security.spec.ECParameterSpec params = pubKey.getParams(); + + return new ECDSAPublicKey( + usage, + ((ECFieldFp)params.getCurve().getField()).getP(), + params.getCurve().getA(), params.getCurve().getB(), + convertPoint(convertCurve(params.getCurve()), params.getGenerator(), false).getEncoded(), + params.getOrder(), + convertPoint(convertCurve(params.getCurve()), pubKey.getW(), false).getEncoded(), + params.getCofactor()); + } + } + + private static org.spongycastle.math.ec.ECPoint convertPoint( + ECCurve curve, + java.security.spec.ECPoint point, + boolean withCompression) + { + return curve.createPoint(point.getAffineX(), point.getAffineY(), withCompression); + } + + private static ECCurve convertCurve( + EllipticCurve ec) + { + ECField field = ec.getField(); + BigInteger a = ec.getA(); + BigInteger b = ec.getB(); + + if (field instanceof ECFieldFp) + { + return new ECCurve.Fp(((ECFieldFp)field).getP(), a, b); + } + else + { + throw new IllegalStateException("not implemented yet!!!"); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/jcajce/NamedEACHelper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/jcajce/NamedEACHelper.java new file mode 100644 index 000000000..7e85142d4 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/jcajce/NamedEACHelper.java @@ -0,0 +1,22 @@ +package org.spongycastle.eac.jcajce; + +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; + +class NamedEACHelper + implements EACHelper +{ + private final String providerName; + + NamedEACHelper(String providerName) + { + this.providerName = providerName; + } + + public KeyFactory createKeyFactory(String type) + throws NoSuchProviderException, NoSuchAlgorithmException + { + return KeyFactory.getInstance(type, providerName); + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/jcajce/ProviderEACHelper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/jcajce/ProviderEACHelper.java new file mode 100644 index 000000000..2cda60a28 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/jcajce/ProviderEACHelper.java @@ -0,0 +1,22 @@ +package org.spongycastle.eac.jcajce; + +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; + +class ProviderEACHelper + implements EACHelper +{ + private final Provider provider; + + ProviderEACHelper(Provider provider) + { + this.provider = provider; + } + + public KeyFactory createKeyFactory(String type) + throws NoSuchAlgorithmException + { + return KeyFactory.getInstance(type, provider); + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/operator/EACSignatureVerifier.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/operator/EACSignatureVerifier.java new file mode 100644 index 000000000..3dd967b91 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/operator/EACSignatureVerifier.java @@ -0,0 +1,30 @@ +package org.spongycastle.eac.operator; + +import java.io.OutputStream; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; + +public interface EACSignatureVerifier +{ + /** + * Return the usage OID specifying the signature type. + * + * @return algorithm oid. + */ + ASN1ObjectIdentifier getUsageIdentifier(); + + /** + * Returns a stream that will accept data for the purpose of calculating + * a signature for later verification. Use org.spongycastle.util.io.TeeOutputStream if you want to accumulate + * the data on the fly as well. + * + * @return an OutputStream + */ + OutputStream getOutputStream(); + + /** + * @param expected expected value of the signature on the data. + * @return true if the signature verifies, false otherwise + */ + boolean verify(byte[] expected); +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/operator/EACSigner.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/operator/EACSigner.java new file mode 100644 index 000000000..9a53685a2 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/operator/EACSigner.java @@ -0,0 +1,27 @@ +package org.spongycastle.eac.operator; + +import java.io.OutputStream; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; + +public interface EACSigner +{ + ASN1ObjectIdentifier getUsageIdentifier(); + + /** + * Returns a stream that will accept data for the purpose of calculating + * a signature. Use org.spongycastle.util.io.TeeOutputStream if you want to accumulate + * the data on the fly as well. + * + * @return an OutputStream + */ + OutputStream getOutputStream(); + + /** + * Returns a signature based on the current data written to the stream, since the + * start or the last call to getSignature(). + * + * @return bytes representing the signature. + */ + byte[] getSignature(); +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/operator/jcajce/DefaultEACHelper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/operator/jcajce/DefaultEACHelper.java new file mode 100644 index 000000000..3aab05859 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/operator/jcajce/DefaultEACHelper.java @@ -0,0 +1,14 @@ +package org.spongycastle.eac.operator.jcajce; + +import java.security.NoSuchAlgorithmException; +import java.security.Signature; + +class DefaultEACHelper + extends EACHelper +{ + protected Signature createSignature(String type) + throws NoSuchAlgorithmException + { + return Signature.getInstance(type); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/operator/jcajce/EACHelper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/operator/jcajce/EACHelper.java new file mode 100644 index 000000000..213699810 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/operator/jcajce/EACHelper.java @@ -0,0 +1,39 @@ +package org.spongycastle.eac.operator.jcajce; + +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Signature; +import java.util.Hashtable; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.eac.EACObjectIdentifiers; + +abstract class EACHelper +{ + private static final Hashtable sigNames = new Hashtable(); + + static + { + sigNames.put(EACObjectIdentifiers.id_TA_RSA_v1_5_SHA_1, "SHA1withRSA"); + sigNames.put(EACObjectIdentifiers.id_TA_RSA_v1_5_SHA_256, "SHA256withRSA"); + sigNames.put(EACObjectIdentifiers.id_TA_RSA_PSS_SHA_1, "SHA1withRSAandMGF1"); + sigNames.put(EACObjectIdentifiers.id_TA_RSA_PSS_SHA_256, "SHA256withRSAandMGF1"); + sigNames.put(EACObjectIdentifiers.id_TA_RSA_v1_5_SHA_512, "SHA512withRSA"); + sigNames.put(EACObjectIdentifiers.id_TA_RSA_PSS_SHA_512, "SHA512withRSAandMGF1"); + + sigNames.put(EACObjectIdentifiers.id_TA_ECDSA_SHA_1, "SHA1withECDSA"); + sigNames.put(EACObjectIdentifiers.id_TA_ECDSA_SHA_224, "SHA224withECDSA"); + sigNames.put(EACObjectIdentifiers.id_TA_ECDSA_SHA_256, "SHA256withECDSA"); + sigNames.put(EACObjectIdentifiers.id_TA_ECDSA_SHA_384, "SHA384withECDSA"); + sigNames.put(EACObjectIdentifiers.id_TA_ECDSA_SHA_512, "SHA512withECDSA"); + } + + public Signature getSignature(ASN1ObjectIdentifier oid) + throws NoSuchProviderException, NoSuchAlgorithmException + { + return createSignature((String)sigNames.get(oid)); + } + + protected abstract Signature createSignature(String type) + throws NoSuchProviderException, NoSuchAlgorithmException; +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/operator/jcajce/EACUtil.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/operator/jcajce/EACUtil.java new file mode 100644 index 000000000..c21178a5d --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/operator/jcajce/EACUtil.java @@ -0,0 +1,5 @@ +package org.spongycastle.eac.operator.jcajce; + +class EACUtil +{ +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/operator/jcajce/JcaEACSignatureVerifierBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/operator/jcajce/JcaEACSignatureVerifierBuilder.java new file mode 100644 index 000000000..1d124cb23 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/operator/jcajce/JcaEACSignatureVerifierBuilder.java @@ -0,0 +1,181 @@ +package org.spongycastle.eac.operator.jcajce; + +import java.io.IOException; +import java.io.OutputStream; +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Provider; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.DERInteger; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.asn1.eac.EACObjectIdentifiers; +import org.spongycastle.eac.operator.EACSignatureVerifier; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.operator.OperatorStreamException; +import org.spongycastle.operator.RuntimeOperatorException; + +public class JcaEACSignatureVerifierBuilder +{ + private EACHelper helper = new DefaultEACHelper(); + + public JcaEACSignatureVerifierBuilder setProvider(String providerName) + { + this.helper = new NamedEACHelper(providerName); + + return this; + } + + public JcaEACSignatureVerifierBuilder setProvider(Provider provider) + { + this.helper = new ProviderEACHelper(provider); + + return this; + } + + public EACSignatureVerifier build(final ASN1ObjectIdentifier usageOid, PublicKey pubKey) + throws OperatorCreationException + { + Signature sig; + try + { + sig = helper.getSignature(usageOid); + + sig.initVerify(pubKey); + } + catch (NoSuchAlgorithmException e) + { + throw new OperatorCreationException("unable to find algorithm: " + e.getMessage(), e); + } + catch (NoSuchProviderException e) + { + throw new OperatorCreationException("unable to find provider: " + e.getMessage(), e); + } + catch (InvalidKeyException e) + { + throw new OperatorCreationException("invalid key: " + e.getMessage(), e); + } + + final SignatureOutputStream sigStream = new SignatureOutputStream(sig); + + return new EACSignatureVerifier() + { + public ASN1ObjectIdentifier getUsageIdentifier() + { + return usageOid; + } + + public OutputStream getOutputStream() + { + return sigStream; + } + + public boolean verify(byte[] expected) + { + try + { + if (usageOid.on(EACObjectIdentifiers.id_TA_ECDSA)) + { + try + { + byte[] reencoded = derEncode(expected); + + return sigStream.verify(reencoded); + } + catch (Exception e) + { + return false; + } + } + else + { + return sigStream.verify(expected); + } + } + catch (SignatureException e) + { + throw new RuntimeOperatorException("exception obtaining signature: " + e.getMessage(), e); + } + } + }; + } + + private static byte[] derEncode(byte[] rawSign) throws IOException + { + int len = rawSign.length / 2; + + byte[] r = new byte[len]; + byte[] s = new byte[len]; + System.arraycopy(rawSign, 0, r, 0, len); + System.arraycopy(rawSign, len, s, 0, len); + + ASN1EncodableVector v = new ASN1EncodableVector(); + v.add(new DERInteger(new BigInteger(1, r))); + v.add(new DERInteger(new BigInteger(1, s))); + + DERSequence seq = new DERSequence(v); + return seq.getEncoded(); + } + + private class SignatureOutputStream + extends OutputStream + { + private Signature sig; + + SignatureOutputStream(Signature sig) + { + this.sig = sig; + } + + public void write(byte[] bytes, int off, int len) + throws IOException + { + try + { + sig.update(bytes, off, len); + } + catch (SignatureException e) + { + throw new OperatorStreamException("exception in content signer: " + e.getMessage(), e); + } + } + + public void write(byte[] bytes) + throws IOException + { + try + { + sig.update(bytes); + } + catch (SignatureException e) + { + throw new OperatorStreamException("exception in content signer: " + e.getMessage(), e); + } + } + + public void write(int b) + throws IOException + { + try + { + sig.update((byte)b); + } + catch (SignatureException e) + { + throw new OperatorStreamException("exception in content signer: " + e.getMessage(), e); + } + } + + boolean verify(byte[] expected) + throws SignatureException + { + return sig.verify(expected); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/operator/jcajce/JcaEACSignerBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/operator/jcajce/JcaEACSignerBuilder.java new file mode 100644 index 000000000..460bb5cf1 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/operator/jcajce/JcaEACSignerBuilder.java @@ -0,0 +1,234 @@ +package org.spongycastle.eac.operator.jcajce; + +import java.io.IOException; +import java.io.OutputStream; +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.Signature; +import java.security.SignatureException; +import java.util.Arrays; +import java.util.Hashtable; + +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.eac.EACObjectIdentifiers; +import org.spongycastle.eac.operator.EACSigner; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.operator.OperatorStreamException; +import org.spongycastle.operator.RuntimeOperatorException; + +public class JcaEACSignerBuilder +{ + private static final Hashtable sigNames = new Hashtable(); + + static + { + sigNames.put("SHA1withRSA", EACObjectIdentifiers.id_TA_RSA_v1_5_SHA_1); + sigNames.put("SHA256withRSA", EACObjectIdentifiers.id_TA_RSA_v1_5_SHA_256); + sigNames.put("SHA1withRSAandMGF1", EACObjectIdentifiers.id_TA_RSA_PSS_SHA_1); + sigNames.put("SHA256withRSAandMGF1", EACObjectIdentifiers.id_TA_RSA_PSS_SHA_256); + sigNames.put("SHA512withRSA", EACObjectIdentifiers.id_TA_RSA_v1_5_SHA_512); + sigNames.put("SHA512withRSAandMGF1", EACObjectIdentifiers.id_TA_RSA_PSS_SHA_512); + + sigNames.put("SHA1withECDSA", EACObjectIdentifiers.id_TA_ECDSA_SHA_1); + sigNames.put("SHA224withECDSA", EACObjectIdentifiers.id_TA_ECDSA_SHA_224); + sigNames.put("SHA256withECDSA", EACObjectIdentifiers.id_TA_ECDSA_SHA_256); + sigNames.put("SHA384withECDSA", EACObjectIdentifiers.id_TA_ECDSA_SHA_384); + sigNames.put("SHA512withECDSA", EACObjectIdentifiers.id_TA_ECDSA_SHA_512); + } + + private EACHelper helper = new DefaultEACHelper(); + + public JcaEACSignerBuilder setProvider(String providerName) + { + this.helper = new NamedEACHelper(providerName); + + return this; + } + + public JcaEACSignerBuilder setProvider(Provider provider) + { + this.helper = new ProviderEACHelper(provider); + + return this; + } + + public EACSigner build(String algorithm, PrivateKey privKey) + throws OperatorCreationException + { + return build((ASN1ObjectIdentifier)sigNames.get(algorithm), privKey); + } + + public EACSigner build(final ASN1ObjectIdentifier usageOid, PrivateKey privKey) + throws OperatorCreationException + { + Signature sig; + try + { + sig = helper.getSignature(usageOid); + + sig.initSign(privKey); + } + catch (NoSuchAlgorithmException e) + { + throw new OperatorCreationException("unable to find algorithm: " + e.getMessage(), e); + } + catch (NoSuchProviderException e) + { + throw new OperatorCreationException("unable to find provider: " + e.getMessage(), e); + } + catch (InvalidKeyException e) + { + throw new OperatorCreationException("invalid key: " + e.getMessage(), e); + } + + final SignatureOutputStream sigStream = new SignatureOutputStream(sig); + + return new EACSigner() + { + public ASN1ObjectIdentifier getUsageIdentifier() + { + return usageOid; + } + + public OutputStream getOutputStream() + { + return sigStream; + } + + public byte[] getSignature() + { + try + { + byte[] signature = sigStream.getSignature(); + + if (usageOid.on(EACObjectIdentifiers.id_TA_ECDSA)) + { + return reencode(signature); + } + + return signature; + } + catch (SignatureException e) + { + throw new RuntimeOperatorException("exception obtaining signature: " + e.getMessage(), e); + } + } + }; + } + + public static int max(int el1, int el2) + { + return el1 > el2 ? el1 : el2; + } + + private static byte[] reencode(byte[] rawSign) + { + ASN1Sequence sData = ASN1Sequence.getInstance(rawSign); + + BigInteger r = ASN1Integer.getInstance(sData.getObjectAt(0)).getValue(); + BigInteger s = ASN1Integer.getInstance(sData.getObjectAt(1)).getValue(); + + byte[] rB = r.toByteArray(); + byte[] sB = s.toByteArray(); + + int rLen = unsignedIntLength(rB); + int sLen = unsignedIntLength(sB); + + byte[] ret; + int len = max(rLen, sLen); + + ret = new byte[len * 2]; + Arrays.fill(ret, (byte)0); + + copyUnsignedInt(rB, ret, len - rLen); + copyUnsignedInt(sB, ret, 2 * len - sLen); + + return ret; + } + + private static int unsignedIntLength(byte[] i) + { + int len = i.length; + if (i[0] == 0) + { + len--; + } + + return len; + } + + private static void copyUnsignedInt(byte[] src, byte[] dst, int offset) + { + int len = src.length; + int readoffset = 0; + if (src[0] == 0) + { + len--; + readoffset = 1; + } + + System.arraycopy(src, readoffset, dst, offset, len); + } + + private class SignatureOutputStream + extends OutputStream + { + private Signature sig; + + SignatureOutputStream(Signature sig) + { + this.sig = sig; + } + + public void write(byte[] bytes, int off, int len) + throws IOException + { + try + { + sig.update(bytes, off, len); + } + catch (SignatureException e) + { + throw new OperatorStreamException("exception in content signer: " + e.getMessage(), e); + } + } + + public void write(byte[] bytes) + throws IOException + { + try + { + sig.update(bytes); + } + catch (SignatureException e) + { + throw new OperatorStreamException("exception in content signer: " + e.getMessage(), e); + } + } + + public void write(int b) + throws IOException + { + try + { + sig.update((byte)b); + } + catch (SignatureException e) + { + throw new OperatorStreamException("exception in content signer: " + e.getMessage(), e); + } + } + + byte[] getSignature() + throws SignatureException + { + return sig.sign(); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/operator/jcajce/NamedEACHelper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/operator/jcajce/NamedEACHelper.java new file mode 100644 index 000000000..129356c28 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/operator/jcajce/NamedEACHelper.java @@ -0,0 +1,22 @@ +package org.spongycastle.eac.operator.jcajce; + +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Signature; + +class NamedEACHelper + extends EACHelper +{ + private final String providerName; + + NamedEACHelper(String providerName) + { + this.providerName = providerName; + } + + protected Signature createSignature(String type) + throws NoSuchProviderException, NoSuchAlgorithmException + { + return Signature.getInstance(type, providerName); + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/operator/jcajce/ProviderEACHelper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/operator/jcajce/ProviderEACHelper.java new file mode 100644 index 000000000..e222dbdb6 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/eac/operator/jcajce/ProviderEACHelper.java @@ -0,0 +1,22 @@ +package org.spongycastle.eac.operator.jcajce; + +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.Signature; + +class ProviderEACHelper + extends EACHelper +{ + private final Provider provider; + + ProviderEACHelper(Provider provider) + { + this.provider = provider; + } + + protected Signature createSignature(String type) + throws NoSuchAlgorithmException + { + return Signature.getInstance(type, provider); + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/mozilla/SignedPublicKeyAndChallenge.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/mozilla/SignedPublicKeyAndChallenge.java new file mode 100644 index 000000000..3244663a3 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/mozilla/SignedPublicKeyAndChallenge.java @@ -0,0 +1,139 @@ +package org.spongycastle.mozilla; + +import java.io.ByteArrayInputStream; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.spec.X509EncodedKeySpec; + +import org.spongycastle.asn1.ASN1InputStream; +import org.spongycastle.asn1.ASN1Object; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.DERBitString; +import org.spongycastle.asn1.mozilla.PublicKeyAndChallenge; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; + +/** + * This is designed to parse the SignedPublicKeyAndChallenge created by the + * KEYGEN tag included by Mozilla based browsers. + * <pre> + * PublicKeyAndChallenge ::= SEQUENCE { + * spki SubjectPublicKeyInfo, + * challenge IA5STRING + * } + * + * SignedPublicKeyAndChallenge ::= SEQUENCE { + * publicKeyAndChallenge PublicKeyAndChallenge, + * signatureAlgorithm AlgorithmIdentifier, + * signature BIT STRING + * } + * </pre> + */ +public class SignedPublicKeyAndChallenge + extends ASN1Object +{ + private static ASN1Sequence toDERSequence(byte[] bytes) + { + try + { + ByteArrayInputStream bIn = new ByteArrayInputStream(bytes); + ASN1InputStream aIn = new ASN1InputStream(bIn); + + return (ASN1Sequence)aIn.readObject(); + } + catch (Exception e) + { + throw new IllegalArgumentException("badly encoded request"); + } + } + + private ASN1Sequence spkacSeq; + private PublicKeyAndChallenge pkac; + private AlgorithmIdentifier signatureAlgorithm; + private DERBitString signature; + + public SignedPublicKeyAndChallenge(byte[] bytes) + { + spkacSeq = toDERSequence(bytes); + pkac = PublicKeyAndChallenge.getInstance(spkacSeq.getObjectAt(0)); + signatureAlgorithm = + AlgorithmIdentifier.getInstance(spkacSeq.getObjectAt(1)); + signature = (DERBitString)spkacSeq.getObjectAt(2); + } + + public ASN1Primitive toASN1Primitive() + { + return spkacSeq; + } + + public PublicKeyAndChallenge getPublicKeyAndChallenge() + { + return pkac; + } + + public boolean verify() + throws NoSuchAlgorithmException, SignatureException, + NoSuchProviderException, InvalidKeyException + { + return verify(null); + } + + public boolean verify(String provider) + throws NoSuchAlgorithmException, SignatureException, + NoSuchProviderException, InvalidKeyException + { + Signature sig = null; + if (provider == null) + { + sig = Signature.getInstance(signatureAlgorithm.getAlgorithm().getId()); + } + else + { + sig = Signature.getInstance(signatureAlgorithm.getAlgorithm().getId(), provider); + } + PublicKey pubKey = this.getPublicKey(provider); + sig.initVerify(pubKey); + try + { + DERBitString pkBytes = new DERBitString(pkac); + sig.update(pkBytes.getBytes()); + + return sig.verify(signature.getBytes()); + } + catch (Exception e) + { + throw new InvalidKeyException("error encoding public key"); + } + } + + public PublicKey getPublicKey(String provider) + throws NoSuchAlgorithmException, NoSuchProviderException, + InvalidKeyException + { + SubjectPublicKeyInfo subjectPKInfo = pkac.getSubjectPublicKeyInfo(); + try + { + DERBitString bStr = new DERBitString(subjectPKInfo); + X509EncodedKeySpec xspec = new X509EncodedKeySpec(bStr.getBytes()); + + + AlgorithmIdentifier keyAlg = subjectPKInfo.getAlgorithm(); + + KeyFactory factory = + KeyFactory.getInstance(keyAlg.getAlgorithm().getId(),provider); + + return factory.generatePublic(xspec); + + } + catch (Exception e) + { + throw new InvalidKeyException("error encoding public key"); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/EncryptionException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/EncryptionException.java new file mode 100644 index 000000000..64ef73eb3 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/EncryptionException.java @@ -0,0 +1,23 @@ +package org.spongycastle.openssl; + +public class EncryptionException + extends PEMException +{ + private Throwable cause; + + public EncryptionException(String msg) + { + super(msg); + } + + public EncryptionException(String msg, Throwable ex) + { + super(msg); + this.cause = ex; + } + + public Throwable getCause() + { + return cause; + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/MiscPEMGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/MiscPEMGenerator.java new file mode 100644 index 000000000..4f00d7aec --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/MiscPEMGenerator.java @@ -0,0 +1,211 @@ +package org.spongycastle.openssl; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.DERInteger; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.asn1.cms.ContentInfo; +import org.spongycastle.asn1.oiw.OIWObjectIdentifiers; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.pkcs.PrivateKeyInfo; +import org.spongycastle.asn1.x509.DSAParameter; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.asn1.x9.X9ObjectIdentifiers; +import org.spongycastle.cert.X509AttributeCertificateHolder; +import org.spongycastle.cert.X509CRLHolder; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.pkcs.PKCS10CertificationRequest; +import org.spongycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; +import org.spongycastle.util.Strings; +import org.spongycastle.util.io.pem.PemGenerationException; +import org.spongycastle.util.io.pem.PemHeader; +import org.spongycastle.util.io.pem.PemObject; +import org.spongycastle.util.io.pem.PemObjectGenerator; + +/** + * PEM generator for the original set of PEM objects used in Open SSL. + */ +public class MiscPEMGenerator + implements PemObjectGenerator +{ + private static final ASN1ObjectIdentifier[] dsaOids = + { + X9ObjectIdentifiers.id_dsa, + OIWObjectIdentifiers.dsaWithSHA1 + }; + + private static final byte[] hexEncodingTable = + { + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', + (byte)'8', (byte)'9', (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F' + }; + + private final Object obj; + private final PEMEncryptor encryptor; + + public MiscPEMGenerator(Object o) + { + this.obj = o; // use of this confuses some earlier JDKs. + this.encryptor = null; + } + + public MiscPEMGenerator(Object o, PEMEncryptor encryptor) + { + this.obj = o; + this.encryptor = encryptor; + } + + private PemObject createPemObject(Object o) + throws IOException + { + String type; + byte[] encoding; + + if (o instanceof PemObject) + { + return (PemObject)o; + } + if (o instanceof PemObjectGenerator) + { + return ((PemObjectGenerator)o).generate(); + } + if (o instanceof X509CertificateHolder) + { + type = "CERTIFICATE"; + + encoding = ((X509CertificateHolder)o).getEncoded(); + } + else if (o instanceof X509CRLHolder) + { + type = "X509 CRL"; + + encoding = ((X509CRLHolder)o).getEncoded(); + } + else if (o instanceof PrivateKeyInfo) + { + PrivateKeyInfo info = (PrivateKeyInfo)o; + ASN1ObjectIdentifier algOID = info.getPrivateKeyAlgorithm().getAlgorithm(); + + if (algOID.equals(PKCSObjectIdentifiers.rsaEncryption)) + { + type = "RSA PRIVATE KEY"; + + encoding = info.parsePrivateKey().toASN1Primitive().getEncoded(); + } + else if (algOID.equals(dsaOids[0]) || algOID.equals(dsaOids[1])) + { + type = "DSA PRIVATE KEY"; + + DSAParameter p = DSAParameter.getInstance(info.getPrivateKeyAlgorithm().getParameters()); + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(new DERInteger(0)); + v.add(new DERInteger(p.getP())); + v.add(new DERInteger(p.getQ())); + v.add(new DERInteger(p.getG())); + + BigInteger x = ASN1Integer.getInstance(info.parsePrivateKey()).getValue(); + BigInteger y = p.getG().modPow(x, p.getP()); + + v.add(new DERInteger(y)); + v.add(new DERInteger(x)); + + encoding = new DERSequence(v).getEncoded(); + } + else if (algOID.equals(X9ObjectIdentifiers.id_ecPublicKey)) + { + type = "EC PRIVATE KEY"; + + encoding = info.parsePrivateKey().toASN1Primitive().getEncoded(); + } + else + { + throw new IOException("Cannot identify private key"); + } + } + else if (o instanceof SubjectPublicKeyInfo) + { + type = "PUBLIC KEY"; + + encoding = ((SubjectPublicKeyInfo)o).getEncoded(); + } + else if (o instanceof X509AttributeCertificateHolder) + { + type = "ATTRIBUTE CERTIFICATE"; + encoding = ((X509AttributeCertificateHolder)o).getEncoded(); + } + else if (o instanceof org.spongycastle.pkcs.PKCS10CertificationRequest) + { + type = "CERTIFICATE REQUEST"; + encoding = ((PKCS10CertificationRequest)o).getEncoded(); + } + else if (o instanceof ContentInfo) + { + type = "PKCS7"; + encoding = ((ContentInfo)o).getEncoded(); + } + else + { + throw new PemGenerationException("unknown object passed - can't encode."); + } + + if (encryptor != null) + { + String dekAlgName = Strings.toUpperCase(encryptor.getAlgorithm()); + + // Note: For backward compatibility + if (dekAlgName.equals("DESEDE")) + { + dekAlgName = "DES-EDE3-CBC"; + } + + + byte[] iv = encryptor.getIV(); + + byte[] encData = encryptor.encrypt(encoding); + + List headers = new ArrayList(2); + + headers.add(new PemHeader("Proc-Type", "4,ENCRYPTED")); + headers.add(new PemHeader("DEK-Info", dekAlgName + "," + getHexEncoded(iv))); + + return new PemObject(type, headers, encData); + } + return new PemObject(type, encoding); + } + + private String getHexEncoded(byte[] bytes) + throws IOException + { + char[] chars = new char[bytes.length * 2]; + + for (int i = 0; i != bytes.length; i++) + { + int v = bytes[i] & 0xff; + + chars[2 * i] = (char)(hexEncodingTable[(v >>> 4)]); + chars[2 * i + 1] = (char)(hexEncodingTable[v & 0xf]); + } + + return new String(chars); + } + + public PemObject generate() + throws PemGenerationException + { + try + { + return createPemObject(obj); + } + catch (IOException e) + { + throw new PemGenerationException("encoding exception: " + e.getMessage(), e); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PEMDecryptor.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PEMDecryptor.java new file mode 100644 index 000000000..3bd54df68 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PEMDecryptor.java @@ -0,0 +1,7 @@ +package org.spongycastle.openssl; + +public interface PEMDecryptor +{ + byte[] decrypt(byte[] keyBytes, byte[] iv) + throws PEMException; +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PEMDecryptorProvider.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PEMDecryptorProvider.java new file mode 100644 index 000000000..0d0b1e5c7 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PEMDecryptorProvider.java @@ -0,0 +1,9 @@ +package org.spongycastle.openssl; + +import org.spongycastle.operator.OperatorCreationException; + +public interface PEMDecryptorProvider +{ + PEMDecryptor get(String dekAlgName) + throws OperatorCreationException; +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PEMEncryptedKeyPair.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PEMEncryptedKeyPair.java new file mode 100644 index 000000000..4a04de149 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PEMEncryptedKeyPair.java @@ -0,0 +1,44 @@ +package org.spongycastle.openssl; + +import java.io.IOException; + +import org.spongycastle.operator.OperatorCreationException; + +public class PEMEncryptedKeyPair +{ + private final String dekAlgName; + private final byte[] iv; + private final byte[] keyBytes; + private final PEMKeyPairParser parser; + + PEMEncryptedKeyPair(String dekAlgName, byte[] iv, byte[] keyBytes, PEMKeyPairParser parser) + { + this.dekAlgName = dekAlgName; + this.iv = iv; + this.keyBytes = keyBytes; + this.parser = parser; + } + + public PEMKeyPair decryptKeyPair(PEMDecryptorProvider keyDecryptorProvider) + throws IOException + { + try + { + PEMDecryptor keyDecryptor = keyDecryptorProvider.get(dekAlgName); + + return parser.parse(keyDecryptor.decrypt(keyBytes, iv)); + } + catch (IOException e) + { + throw e; + } + catch (OperatorCreationException e) + { + throw new PEMException("cannot create extraction operator: " + e.getMessage(), e); + } + catch (Exception e) + { + throw new PEMException("exception processing key pair: " + e.getMessage(), e); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PEMEncryptor.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PEMEncryptor.java new file mode 100644 index 000000000..63b42b2dd --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PEMEncryptor.java @@ -0,0 +1,11 @@ +package org.spongycastle.openssl; + +public interface PEMEncryptor +{ + String getAlgorithm(); + + byte[] getIV(); + + byte[] encrypt(byte[] encoding) + throws PEMException; +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PEMException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PEMException.java new file mode 100644 index 000000000..5df7b5f53 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PEMException.java @@ -0,0 +1,34 @@ +package org.spongycastle.openssl; + +import java.io.IOException; + +public class PEMException + extends IOException +{ + Exception underlying; + + public PEMException( + String message) + { + super(message); + } + + public PEMException( + String message, + Exception underlying) + { + super(message); + this.underlying = underlying; + } + + public Exception getUnderlyingException() + { + return underlying; + } + + + public Throwable getCause() + { + return underlying; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PEMKeyPair.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PEMKeyPair.java new file mode 100644 index 000000000..ad0106344 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PEMKeyPair.java @@ -0,0 +1,26 @@ +package org.spongycastle.openssl; + +import org.spongycastle.asn1.pkcs.PrivateKeyInfo; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; + +public class PEMKeyPair +{ + private final SubjectPublicKeyInfo publicKeyInfo; + private final PrivateKeyInfo privateKeyInfo; + + public PEMKeyPair(SubjectPublicKeyInfo publicKeyInfo, PrivateKeyInfo privateKeyInfo) + { + this.publicKeyInfo = publicKeyInfo; + this.privateKeyInfo = privateKeyInfo; + } + + public PrivateKeyInfo getPrivateKeyInfo() + { + return privateKeyInfo; + } + + public SubjectPublicKeyInfo getPublicKeyInfo() + { + return publicKeyInfo; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PEMKeyPairParser.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PEMKeyPairParser.java new file mode 100644 index 000000000..32583fd99 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PEMKeyPairParser.java @@ -0,0 +1,9 @@ +package org.spongycastle.openssl; + +import java.io.IOException; + +interface PEMKeyPairParser +{ + PEMKeyPair parse(byte[] encoding) + throws IOException; +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PEMParser.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PEMParser.java new file mode 100644 index 000000000..91b1cff0b --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PEMParser.java @@ -0,0 +1,509 @@ +package org.spongycastle.openssl; + +import java.io.IOException; +import java.io.Reader; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +import org.spongycastle.asn1.ASN1InputStream; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.DERNull; +import org.spongycastle.asn1.cms.ContentInfo; +import org.spongycastle.asn1.pkcs.EncryptedPrivateKeyInfo; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.pkcs.PrivateKeyInfo; +import org.spongycastle.asn1.pkcs.RSAPublicKey; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.DSAParameter; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.asn1.x9.X9ECParameters; +import org.spongycastle.asn1.x9.X9ObjectIdentifiers; +import org.spongycastle.cert.X509AttributeCertificateHolder; +import org.spongycastle.cert.X509CRLHolder; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.pkcs.PKCS10CertificationRequest; +import org.spongycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; +import org.spongycastle.util.encoders.Hex; +import org.spongycastle.util.io.pem.PemHeader; +import org.spongycastle.util.io.pem.PemObject; +import org.spongycastle.util.io.pem.PemObjectParser; +import org.spongycastle.util.io.pem.PemReader; + +/** + * Class for parsing OpenSSL PEM encoded streams containing + * X509 certificates, PKCS8 encoded keys and PKCS7 objects. + * <p> + * In the case of PKCS7 objects the reader will return a CMS ContentInfo object. Public keys will be returned as + * well formed SubjectPublicKeyInfo objects, private keys will be returned as well formed PrivateKeyInfo objects. In the + * case of a private key a PEMKeyPair will normally be returned if the encoding contains both the private and public + * key definition. CRLs, Certificates, PKCS#10 requests, and Attribute Certificates will generate the appropriate BC holder class. + * </p> + */ +public class PEMParser + extends PemReader +{ + private final Map parsers = new HashMap(); + + /** + * Create a new PEMReader + * + * @param reader the Reader + */ + public PEMParser( + Reader reader) + { + super(reader); + + parsers.put("CERTIFICATE REQUEST", new PKCS10CertificationRequestParser()); + parsers.put("NEW CERTIFICATE REQUEST", new PKCS10CertificationRequestParser()); + parsers.put("CERTIFICATE", new X509CertificateParser()); + parsers.put("X509 CERTIFICATE", new X509CertificateParser()); + parsers.put("X509 CRL", new X509CRLParser()); + parsers.put("PKCS7", new PKCS7Parser()); + parsers.put("ATTRIBUTE CERTIFICATE", new X509AttributeCertificateParser()); + parsers.put("EC PARAMETERS", new ECCurveParamsParser()); + parsers.put("PUBLIC KEY", new PublicKeyParser()); + parsers.put("RSA PUBLIC KEY", new RSAPublicKeyParser()); + parsers.put("RSA PRIVATE KEY", new KeyPairParser(new RSAKeyPairParser())); + parsers.put("DSA PRIVATE KEY", new KeyPairParser(new DSAKeyPairParser())); + parsers.put("EC PRIVATE KEY", new KeyPairParser(new ECDSAKeyPairParser())); + parsers.put("ENCRYPTED PRIVATE KEY", new EncryptedPrivateKeyParser()); + parsers.put("PRIVATE KEY", new PrivateKeyParser()); + } + + public Object readObject() + throws IOException + { + PemObject obj = readPemObject(); + + if (obj != null) + { + String type = obj.getType(); + if (parsers.containsKey(type)) + { + return ((PemObjectParser)parsers.get(type)).parseObject(obj); + } + else + { + throw new IOException("unrecognised object: " + type); + } + } + + return null; + } + + private class KeyPairParser + implements PemObjectParser + { + private final PEMKeyPairParser pemKeyPairParser; + + public KeyPairParser(PEMKeyPairParser pemKeyPairParser) + { + this.pemKeyPairParser = pemKeyPairParser; + } + + /** + * Read a Key Pair + */ + public Object parseObject( + PemObject obj) + throws IOException + { + boolean isEncrypted = false; + String dekInfo = null; + List headers = obj.getHeaders(); + + for (Iterator it = headers.iterator(); it.hasNext();) + { + PemHeader hdr = (PemHeader)it.next(); + + if (hdr.getName().equals("Proc-Type") && hdr.getValue().equals("4,ENCRYPTED")) + { + isEncrypted = true; + } + else if (hdr.getName().equals("DEK-Info")) + { + dekInfo = hdr.getValue(); + } + } + + // + // extract the key + // + byte[] keyBytes = obj.getContent(); + + try + { + if (isEncrypted) + { + StringTokenizer tknz = new StringTokenizer(dekInfo, ","); + String dekAlgName = tknz.nextToken(); + byte[] iv = Hex.decode(tknz.nextToken()); + + return new PEMEncryptedKeyPair(dekAlgName, iv, keyBytes, pemKeyPairParser); + } + + return pemKeyPairParser.parse(keyBytes); + } + catch (IOException e) + { + if (isEncrypted) + { + throw new PEMException("exception decoding - please check password and data.", e); + } + else + { + throw new PEMException(e.getMessage(), e); + } + } + catch (IllegalArgumentException e) + { + if (isEncrypted) + { + throw new PEMException("exception decoding - please check password and data.", e); + } + else + { + throw new PEMException(e.getMessage(), e); + } + } + } + } + + private class DSAKeyPairParser + implements PEMKeyPairParser + { + public PEMKeyPair parse(byte[] encoding) + throws IOException + { + try + { + ASN1Sequence seq = ASN1Sequence.getInstance(encoding); + + if (seq.size() != 6) + { + throw new PEMException("malformed sequence in DSA private key"); + } + + // ASN1Integer v = (ASN1Integer)seq.getObjectAt(0); + ASN1Integer p = ASN1Integer.getInstance(seq.getObjectAt(1)); + ASN1Integer q = ASN1Integer.getInstance(seq.getObjectAt(2)); + ASN1Integer g = ASN1Integer.getInstance(seq.getObjectAt(3)); + ASN1Integer y = ASN1Integer.getInstance(seq.getObjectAt(4)); + ASN1Integer x = ASN1Integer.getInstance(seq.getObjectAt(5)); + + return new PEMKeyPair( + new SubjectPublicKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa, new DSAParameter(p.getValue(), q.getValue(), g.getValue())), y), + new PrivateKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa, new DSAParameter(p.getValue(), q.getValue(), g.getValue())), x)); + } + catch (IOException e) + { + throw e; + } + catch (Exception e) + { + throw new PEMException( + "problem creating DSA private key: " + e.toString(), e); + } + } + } + + private class ECDSAKeyPairParser + implements PEMKeyPairParser + { + public PEMKeyPair parse(byte[] encoding) + throws IOException + { + try + { + ASN1Sequence seq = ASN1Sequence.getInstance(encoding); + + org.spongycastle.asn1.sec.ECPrivateKey pKey = org.spongycastle.asn1.sec.ECPrivateKey.getInstance(seq); + AlgorithmIdentifier algId = new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, pKey.getParameters()); + PrivateKeyInfo privInfo = new PrivateKeyInfo(algId, pKey); + SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo(algId, pKey.getPublicKey().getBytes()); + + return new PEMKeyPair(pubInfo, privInfo); + } + catch (IOException e) + { + throw e; + } + catch (Exception e) + { + throw new PEMException( + "problem creating EC private key: " + e.toString(), e); + } + } + } + + private class RSAKeyPairParser + implements PEMKeyPairParser + { + public PEMKeyPair parse(byte[] encoding) + throws IOException + { + try + { + ASN1Sequence seq = ASN1Sequence.getInstance(encoding); + + if (seq.size() != 9) + { + throw new PEMException("malformed sequence in RSA private key"); + } + + org.spongycastle.asn1.pkcs.RSAPrivateKey keyStruct = org.spongycastle.asn1.pkcs.RSAPrivateKey.getInstance(seq); + + RSAPublicKey pubSpec = new RSAPublicKey( + keyStruct.getModulus(), keyStruct.getPublicExponent()); + + AlgorithmIdentifier algId = new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE); + + return new PEMKeyPair(new SubjectPublicKeyInfo(algId, pubSpec), new PrivateKeyInfo(algId, keyStruct)); + } + catch (IOException e) + { + throw e; + } + catch (Exception e) + { + throw new PEMException( + "problem creating RSA private key: " + e.toString(), e); + } + } + } + + private class PublicKeyParser + implements PemObjectParser + { + public PublicKeyParser() + { + } + + public Object parseObject(PemObject obj) + throws IOException + { + return SubjectPublicKeyInfo.getInstance(obj.getContent()); + } + } + + private class RSAPublicKeyParser + implements PemObjectParser + { + public RSAPublicKeyParser() + { + } + + public Object parseObject(PemObject obj) + throws IOException + { + try + { + RSAPublicKey rsaPubStructure = RSAPublicKey.getInstance(obj.getContent()); + + return new SubjectPublicKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), rsaPubStructure); + } + catch (IOException e) + { + throw e; + } + catch (Exception e) + { + throw new PEMException("problem extracting key: " + e.toString(), e); + } + } + } + + private class X509CertificateParser + implements PemObjectParser + { + /** + * Reads in a X509Certificate. + * + * @return the X509Certificate + * @throws java.io.IOException if an I/O error occured + */ + public Object parseObject(PemObject obj) + throws IOException + { + try + { + return new X509CertificateHolder(obj.getContent()); + } + catch (Exception e) + { + throw new PEMException("problem parsing cert: " + e.toString(), e); + } + } + } + + private class X509CRLParser + implements PemObjectParser + { + /** + * Reads in a X509CRL. + * + * @return the X509Certificate + * @throws java.io.IOException if an I/O error occured + */ + public Object parseObject(PemObject obj) + throws IOException + { + try + { + return new X509CRLHolder(obj.getContent()); + } + catch (Exception e) + { + throw new PEMException("problem parsing cert: " + e.toString(), e); + } + } + } + + private class PKCS10CertificationRequestParser + implements PemObjectParser + { + /** + * Reads in a PKCS10 certification request. + * + * @return the certificate request. + * @throws java.io.IOException if an I/O error occured + */ + public Object parseObject(PemObject obj) + throws IOException + { + try + { + return new PKCS10CertificationRequest(obj.getContent()); + } + catch (Exception e) + { + throw new PEMException("problem parsing certrequest: " + e.toString(), e); + } + } + } + + private class PKCS7Parser + implements PemObjectParser + { + /** + * Reads in a PKCS7 object. This returns a ContentInfo object suitable for use with the CMS + * API. + * + * @return the X509Certificate + * @throws java.io.IOException if an I/O error occured + */ + public Object parseObject(PemObject obj) + throws IOException + { + try + { + ASN1InputStream aIn = new ASN1InputStream(obj.getContent()); + + return ContentInfo.getInstance(aIn.readObject()); + } + catch (Exception e) + { + throw new PEMException("problem parsing PKCS7 object: " + e.toString(), e); + } + } + } + + private class X509AttributeCertificateParser + implements PemObjectParser + { + public Object parseObject(PemObject obj) + throws IOException + { + return new X509AttributeCertificateHolder(obj.getContent()); + } + } + + private class ECCurveParamsParser + implements PemObjectParser + { + public Object parseObject(PemObject obj) + throws IOException + { + try + { + Object param = ASN1Primitive.fromByteArray(obj.getContent()); + + if (param instanceof ASN1ObjectIdentifier) + { + return ASN1Primitive.fromByteArray(obj.getContent()); + } + else if (param instanceof ASN1Sequence) + { + return X9ECParameters.getInstance(param); + } + else + { + return null; // implicitly CA + } + } + catch (IOException e) + { + throw e; + } + catch (Exception e) + { + throw new PEMException("exception extracting EC named curve: " + e.toString()); + } + } + } + + private class EncryptedPrivateKeyParser + implements PemObjectParser + { + public EncryptedPrivateKeyParser() + { + } + + /** + * Reads in an EncryptedPrivateKeyInfo + * + * @return the X509Certificate + * @throws java.io.IOException if an I/O error occured + */ + public Object parseObject(PemObject obj) + throws IOException + { + try + { + return new PKCS8EncryptedPrivateKeyInfo(EncryptedPrivateKeyInfo.getInstance(obj.getContent())); + } + catch (Exception e) + { + throw new PEMException("problem parsing ENCRYPTED PRIVATE KEY: " + e.toString(), e); + } + } + } + + private class PrivateKeyParser + implements PemObjectParser + { + public PrivateKeyParser() + { + } + + public Object parseObject(PemObject obj) + throws IOException + { + try + { + return PrivateKeyInfo.getInstance(obj.getContent()); + } + catch (Exception e) + { + throw new PEMException("problem parsing PRIVATE KEY: " + e.toString(), e); + } + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PEMUtilities.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PEMUtilities.java new file mode 100644 index 000000000..59f350060 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PEMUtilities.java @@ -0,0 +1,65 @@ +package org.spongycastle.openssl; + +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.DERObjectIdentifier; +import org.spongycastle.asn1.nist.NISTObjectIdentifiers; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.util.Integers; + +public final class PEMUtilities +{ + private static final Map KEYSIZES = new HashMap(); + private static final Set PKCS5_SCHEME_1 = new HashSet(); + private static final Set PKCS5_SCHEME_2 = new HashSet(); + + static + { + PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithMD2AndDES_CBC); + PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithMD2AndRC2_CBC); + PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithMD5AndDES_CBC); + PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithMD5AndRC2_CBC); + PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithSHA1AndDES_CBC); + PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithSHA1AndRC2_CBC); + + PKCS5_SCHEME_2.add(PKCSObjectIdentifiers.id_PBES2); + PKCS5_SCHEME_2.add(PKCSObjectIdentifiers.des_EDE3_CBC); + PKCS5_SCHEME_2.add(NISTObjectIdentifiers.id_aes128_CBC); + PKCS5_SCHEME_2.add(NISTObjectIdentifiers.id_aes192_CBC); + PKCS5_SCHEME_2.add(NISTObjectIdentifiers.id_aes256_CBC); + + KEYSIZES.put(PKCSObjectIdentifiers.des_EDE3_CBC.getId(), Integers.valueOf(192)); + KEYSIZES.put(NISTObjectIdentifiers.id_aes128_CBC.getId(), Integers.valueOf(128)); + KEYSIZES.put(NISTObjectIdentifiers.id_aes192_CBC.getId(), Integers.valueOf(192)); + KEYSIZES.put(NISTObjectIdentifiers.id_aes256_CBC.getId(), Integers.valueOf(256)); + } + + static int getKeySize(String algorithm) + { + if (!KEYSIZES.containsKey(algorithm)) + { + throw new IllegalStateException("no key size for algorithm: " + algorithm); + } + + return ((Integer)KEYSIZES.get(algorithm)).intValue(); + } + + static boolean isPKCS5Scheme1(DERObjectIdentifier algOid) + { + return PKCS5_SCHEME_1.contains(algOid); + } + + public static boolean isPKCS5Scheme2(ASN1ObjectIdentifier algOid) + { + return PKCS5_SCHEME_2.contains(algOid); + } + + public static boolean isPKCS12(DERObjectIdentifier algOid) + { + return algOid.getId().startsWith(PKCSObjectIdentifiers.pkcs_12PbeIds.getId()); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PEMWriter.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PEMWriter.java new file mode 100644 index 000000000..2046d620a --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PEMWriter.java @@ -0,0 +1,60 @@ +package org.spongycastle.openssl; + +import java.io.IOException; +import java.io.Writer; + +import org.spongycastle.openssl.jcajce.JcaMiscPEMGenerator; +import org.spongycastle.util.io.pem.PemGenerationException; +import org.spongycastle.util.io.pem.PemObjectGenerator; +import org.spongycastle.util.io.pem.PemWriter; + +/** + * General purpose writer for OpenSSL PEM objects. + */ +public class PEMWriter + extends PemWriter +{ + /** + * Base constructor. + * + * @param out output stream to use. + */ + public PEMWriter(Writer out) + { + super(out); + } + + public void writeObject( + Object obj) + throws IOException + { + writeObject(obj, null); + } + + public void writeObject( + Object obj, + PEMEncryptor encryptor) + throws IOException + { + try + { + super.writeObject(new JcaMiscPEMGenerator(obj, encryptor)); + } + catch (PemGenerationException e) + { + if (e.getCause() instanceof IOException) + { + throw (IOException)e.getCause(); + } + + throw e; + } + } + + public void writeObject( + PemObjectGenerator obj) + throws IOException + { + super.writeObject(obj); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PKCS8Generator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PKCS8Generator.java new file mode 100644 index 000000000..83130d367 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PKCS8Generator.java @@ -0,0 +1,87 @@ +package org.spongycastle.openssl; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.nist.NISTObjectIdentifiers; +import org.spongycastle.asn1.pkcs.EncryptedPrivateKeyInfo; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.pkcs.PrivateKeyInfo; +import org.spongycastle.operator.OutputEncryptor; +import org.spongycastle.util.io.pem.PemGenerationException; +import org.spongycastle.util.io.pem.PemObject; +import org.spongycastle.util.io.pem.PemObjectGenerator; + +public class PKCS8Generator + implements PemObjectGenerator +{ + public static final ASN1ObjectIdentifier AES_128_CBC = NISTObjectIdentifiers.id_aes128_CBC; + public static final ASN1ObjectIdentifier AES_192_CBC = NISTObjectIdentifiers.id_aes192_CBC; + public static final ASN1ObjectIdentifier AES_256_CBC = NISTObjectIdentifiers.id_aes256_CBC; + + public static final ASN1ObjectIdentifier DES3_CBC = PKCSObjectIdentifiers.des_EDE3_CBC; + + public static final ASN1ObjectIdentifier PBE_SHA1_RC4_128 = PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC4; + public static final ASN1ObjectIdentifier PBE_SHA1_RC4_40 = PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC4; + public static final ASN1ObjectIdentifier PBE_SHA1_3DES = PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC; + public static final ASN1ObjectIdentifier PBE_SHA1_2DES = PKCSObjectIdentifiers.pbeWithSHAAnd2_KeyTripleDES_CBC; + public static final ASN1ObjectIdentifier PBE_SHA1_RC2_128 = PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC2_CBC; + public static final ASN1ObjectIdentifier PBE_SHA1_RC2_40 = PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC2_CBC; + + private PrivateKeyInfo key; + private OutputEncryptor outputEncryptor; + + /** + * Base constructor. + */ + public PKCS8Generator(PrivateKeyInfo key, OutputEncryptor outputEncryptor) + { + this.key = key; + this.outputEncryptor = outputEncryptor; + } + + public PemObject generate() + throws PemGenerationException + { + if (outputEncryptor != null) + { + return generate(key, outputEncryptor); + } + else + { + return generate(key, null); + } + } + + private PemObject generate(PrivateKeyInfo key, OutputEncryptor encryptor) + throws PemGenerationException + { + try + { + byte[] keyData = key.getEncoded(); + + if (encryptor == null) + { + return new PemObject("PRIVATE KEY", keyData); + } + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + OutputStream cOut = encryptor.getOutputStream(bOut); + + cOut.write(key.getEncoded()); + + cOut.close(); + + EncryptedPrivateKeyInfo info = new EncryptedPrivateKeyInfo(encryptor.getAlgorithmIdentifier(), bOut.toByteArray()); + + return new PemObject("ENCRYPTED PRIVATE KEY", info.getEncoded()); + } + catch (IOException e) + { + throw new PemGenerationException("unable to process encoded key data: " + e.getMessage(), e); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PasswordException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PasswordException.java new file mode 100644 index 000000000..68de32137 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PasswordException.java @@ -0,0 +1,10 @@ +package org.spongycastle.openssl; + +public class PasswordException + extends PEMException +{ + public PasswordException(String msg) + { + super(msg); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PasswordFinder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PasswordFinder.java new file mode 100644 index 000000000..eb981fd51 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/PasswordFinder.java @@ -0,0 +1,9 @@ +package org.spongycastle.openssl; + +/** + * call back to allow a password to be fetched when one is requested. + */ +public interface PasswordFinder +{ + public char[] getPassword(); +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcaMiscPEMGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcaMiscPEMGenerator.java new file mode 100644 index 000000000..88f2a32ec --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcaMiscPEMGenerator.java @@ -0,0 +1,98 @@ +package org.spongycastle.openssl.jcajce; + +import java.io.IOException; +import java.security.Key; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.cert.CRLException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509CRL; +import java.security.cert.X509Certificate; + +import org.spongycastle.asn1.pkcs.PrivateKeyInfo; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.cert.jcajce.JcaX509AttributeCertificateHolder; +import org.spongycastle.cert.jcajce.JcaX509CRLHolder; +import org.spongycastle.cert.jcajce.JcaX509CertificateHolder; +import org.spongycastle.jce.PKCS10CertificationRequest; +import org.spongycastle.openssl.MiscPEMGenerator; +import org.spongycastle.openssl.PEMEncryptor; +import org.spongycastle.x509.X509AttributeCertificate; +import org.spongycastle.x509.X509V2AttributeCertificate; + +/** + * PEM generator for the original set of PEM objects used in Open SSL. + */ +public class JcaMiscPEMGenerator + extends MiscPEMGenerator +{ + private Object obj; + private String algorithm; + private char[] password; + private SecureRandom random; + private Provider provider; + + public JcaMiscPEMGenerator(Object o) + throws IOException + { + super(convertObject(o)); + } + + public JcaMiscPEMGenerator(Object o, PEMEncryptor encryptor) + throws IOException + { + super(convertObject(o), encryptor); + } + + private static Object convertObject(Object o) + throws IOException + { + if (o instanceof X509Certificate) + { + try + { + return new JcaX509CertificateHolder((X509Certificate)o); + } + catch (CertificateEncodingException e) + { + throw new IllegalArgumentException("Cannot encode object: " + e.toString()); + } + } + else if (o instanceof X509CRL) + { + try + { + return new JcaX509CRLHolder((X509CRL)o); + } + catch (CRLException e) + { + throw new IllegalArgumentException("Cannot encode object: " + e.toString()); + } + } + else if (o instanceof KeyPair) + { + return convertObject(((KeyPair)o).getPrivate()); + } + else if (o instanceof PrivateKey) + { + return PrivateKeyInfo.getInstance(((Key)o).getEncoded()); + } + else if (o instanceof PublicKey) + { + return SubjectPublicKeyInfo.getInstance(((PublicKey)o).getEncoded()); + } + else if (o instanceof X509AttributeCertificate) + { + return new JcaX509AttributeCertificateHolder((X509V2AttributeCertificate)o); + } + else if (o instanceof PKCS10CertificationRequest) + { + return new org.spongycastle.pkcs.PKCS10CertificationRequest(((PKCS10CertificationRequest)o).getEncoded()); + } + + return o; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcaPEMKeyConverter.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcaPEMKeyConverter.java new file mode 100644 index 000000000..8fa2b93ed --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcaPEMKeyConverter.java @@ -0,0 +1,115 @@ +package org.spongycastle.openssl.jcajce; + +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.HashMap; +import java.util.Map; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.pkcs.PrivateKeyInfo; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.asn1.x9.X9ObjectIdentifiers; +import org.spongycastle.jcajce.DefaultJcaJceHelper; +import org.spongycastle.jcajce.JcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.ProviderJcaJceHelper; +import org.spongycastle.openssl.PEMException; +import org.spongycastle.openssl.PEMKeyPair; + +public class JcaPEMKeyConverter +{ + private JcaJceHelper helper = new DefaultJcaJceHelper(); + + private static final Map algorithms = new HashMap(); + + static + { + algorithms.put(X9ObjectIdentifiers.id_ecPublicKey, "ECDSA"); + algorithms.put(PKCSObjectIdentifiers.rsaEncryption, "RSA"); + algorithms.put(X9ObjectIdentifiers.id_dsa, "DSA"); + } + + public JcaPEMKeyConverter setProvider(Provider provider) + { + this.helper = new ProviderJcaJceHelper(provider); + + return this; + } + + public JcaPEMKeyConverter setProvider(String providerName) + { + this.helper = new NamedJcaJceHelper(providerName); + + return this; + } + + public KeyPair getKeyPair(PEMKeyPair keyPair) + throws PEMException + { + try + { + KeyFactory keyFactory = getKeyFactory(keyPair.getPrivateKeyInfo().getPrivateKeyAlgorithm()); + + return new KeyPair(keyFactory.generatePublic(new X509EncodedKeySpec(keyPair.getPublicKeyInfo().getEncoded())), + keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyPair.getPrivateKeyInfo().getEncoded()))); + } + catch (Exception e) + { + throw new PEMException("unable to convert key pair: " + e.getMessage(), e); + } + } + + public PublicKey getPublicKey(SubjectPublicKeyInfo publicKeyInfo) + throws PEMException + { + try + { + KeyFactory keyFactory = getKeyFactory(publicKeyInfo.getAlgorithm()); + + return keyFactory.generatePublic(new X509EncodedKeySpec(publicKeyInfo.getEncoded())); + } + catch (Exception e) + { + throw new PEMException("unable to convert key pair: " + e.getMessage(), e); + } + } + + public PrivateKey getPrivateKey(PrivateKeyInfo privateKeyInfo) + throws PEMException + { + try + { + KeyFactory keyFactory = getKeyFactory(privateKeyInfo.getPrivateKeyAlgorithm()); + + return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyInfo.getEncoded())); + } + catch (Exception e) + { + throw new PEMException("unable to convert key pair: " + e.getMessage(), e); + } + } + + private KeyFactory getKeyFactory(AlgorithmIdentifier algId) + throws NoSuchAlgorithmException, NoSuchProviderException + { + ASN1ObjectIdentifier algorithm = algId.getAlgorithm(); + + String algName = (String)algorithms.get(algorithm); + + if (algName == null) + { + algName = algorithm.getId(); + } + + return helper.createKeyFactory(algName); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcaPKCS8Generator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcaPKCS8Generator.java new file mode 100644 index 000000000..9c4e4f46c --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcaPKCS8Generator.java @@ -0,0 +1,18 @@ +package org.spongycastle.openssl.jcajce; + +import java.security.PrivateKey; + +import org.spongycastle.asn1.pkcs.PrivateKeyInfo; +import org.spongycastle.openssl.PKCS8Generator; +import org.spongycastle.operator.OutputEncryptor; +import org.spongycastle.util.io.pem.PemGenerationException; + +public class JcaPKCS8Generator + extends PKCS8Generator +{ + public JcaPKCS8Generator(PrivateKey key, OutputEncryptor encryptor) + throws PemGenerationException + { + super(PrivateKeyInfo.getInstance(key.getEncoded()), encryptor); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/jcajce/JceOpenSSLPKCS8DecryptorProviderBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/jcajce/JceOpenSSLPKCS8DecryptorProviderBuilder.java new file mode 100644 index 000000000..2d8e45349 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/jcajce/JceOpenSSLPKCS8DecryptorProviderBuilder.java @@ -0,0 +1,141 @@ +package org.spongycastle.openssl.jcajce; + +import java.io.IOException; +import java.io.InputStream; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.Provider; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.PBEParameterSpec; + +import org.spongycastle.asn1.pkcs.EncryptionScheme; +import org.spongycastle.asn1.pkcs.KeyDerivationFunc; +import org.spongycastle.asn1.pkcs.PBEParameter; +import org.spongycastle.asn1.pkcs.PBES2Parameters; +import org.spongycastle.asn1.pkcs.PBKDF2Params; +import org.spongycastle.asn1.pkcs.PKCS12PBEParams; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.jcajce.DefaultJcaJceHelper; +import org.spongycastle.jcajce.JcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.ProviderJcaJceHelper; +import org.spongycastle.openssl.PEMException; +import org.spongycastle.operator.InputDecryptor; +import org.spongycastle.operator.InputDecryptorProvider; +import org.spongycastle.operator.OperatorCreationException; + +public class JceOpenSSLPKCS8DecryptorProviderBuilder +{ + private JcaJceHelper helper = new DefaultJcaJceHelper(); + + public JceOpenSSLPKCS8DecryptorProviderBuilder() + { + helper = new DefaultJcaJceHelper(); + } + + public JceOpenSSLPKCS8DecryptorProviderBuilder setProvider(String providerName) + { + helper = new NamedJcaJceHelper(providerName); + + return this; + } + + public JceOpenSSLPKCS8DecryptorProviderBuilder setProvider(Provider provider) + { + helper = new ProviderJcaJceHelper(provider); + + return this; + } + + public InputDecryptorProvider build(final char[] password) + throws OperatorCreationException + { + return new InputDecryptorProvider() + { + public InputDecryptor get(final AlgorithmIdentifier algorithm) + throws OperatorCreationException + { + final Cipher cipher; + + try + { + if (PEMUtilities.isPKCS5Scheme2(algorithm.getAlgorithm())) + { + PBES2Parameters params = PBES2Parameters.getInstance(algorithm.getParameters()); + KeyDerivationFunc func = params.getKeyDerivationFunc(); + EncryptionScheme scheme = params.getEncryptionScheme(); + PBKDF2Params defParams = (PBKDF2Params)func.getParameters(); + + int iterationCount = defParams.getIterationCount().intValue(); + byte[] salt = defParams.getSalt(); + + String oid = scheme.getAlgorithm().getId(); + + SecretKey key = PEMUtilities.generateSecretKeyForPKCS5Scheme2(oid, password, salt, iterationCount); + + cipher = helper.createCipher(oid); + AlgorithmParameters algParams = helper.createAlgorithmParameters(oid); + + algParams.init(scheme.getParameters().toASN1Primitive().getEncoded()); + + cipher.init(Cipher.DECRYPT_MODE, key, algParams); + } + else if (PEMUtilities.isPKCS12(algorithm.getAlgorithm())) + { + PKCS12PBEParams params = PKCS12PBEParams.getInstance(algorithm.getParameters()); + PBEKeySpec pbeSpec = new PBEKeySpec(password); + + SecretKeyFactory secKeyFact = helper.createSecretKeyFactory(algorithm.getAlgorithm().getId()); + PBEParameterSpec defParams = new PBEParameterSpec(params.getIV(), params.getIterations().intValue()); + + cipher = helper.createCipher(algorithm.getAlgorithm().getId()); + + cipher.init(Cipher.DECRYPT_MODE, secKeyFact.generateSecret(pbeSpec), defParams); + } + else if (PEMUtilities.isPKCS5Scheme1(algorithm.getAlgorithm())) + { + PBEParameter params = PBEParameter.getInstance(algorithm.getParameters()); + PBEKeySpec pbeSpec = new PBEKeySpec(password); + + SecretKeyFactory secKeyFact = helper.createSecretKeyFactory(algorithm.getAlgorithm().getId()); + PBEParameterSpec defParams = new PBEParameterSpec(params.getSalt(), params.getIterationCount().intValue()); + + cipher = helper.createCipher(algorithm.getAlgorithm().getId()); + + cipher.init(Cipher.DECRYPT_MODE, secKeyFact.generateSecret(pbeSpec), defParams); + } + else + { + throw new PEMException("Unknown algorithm: " + algorithm.getAlgorithm()); + } + + return new InputDecryptor() + { + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return algorithm; + } + + public InputStream getInputStream(InputStream encIn) + { + return new CipherInputStream(encIn, cipher); + } + }; + } + catch (IOException e) + { + throw new OperatorCreationException(algorithm.getAlgorithm() + " not available: " + e.getMessage(), e); + } + catch (GeneralSecurityException e) + { + throw new OperatorCreationException(algorithm.getAlgorithm() + " not available: " + e.getMessage(), e); + } + }; + }; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/jcajce/JceOpenSSLPKCS8EncryptorBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/jcajce/JceOpenSSLPKCS8EncryptorBuilder.java new file mode 100644 index 000000000..5cfb02b5f --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/jcajce/JceOpenSSLPKCS8EncryptorBuilder.java @@ -0,0 +1,221 @@ +package org.spongycastle.openssl.jcajce; + +import java.io.IOException; +import java.io.OutputStream; +import java.security.AlgorithmParameterGenerator; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +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.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.asn1.nist.NISTObjectIdentifiers; +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.jcajce.DefaultJcaJceHelper; +import org.spongycastle.jcajce.JcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.ProviderJcaJceHelper; +import org.spongycastle.operator.GenericKey; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.operator.OutputEncryptor; +import org.spongycastle.operator.jcajce.JceGenericKey; + +public class JceOpenSSLPKCS8EncryptorBuilder +{ + public static final String AES_128_CBC = NISTObjectIdentifiers.id_aes128_CBC.getId(); + public static final String AES_192_CBC = NISTObjectIdentifiers.id_aes192_CBC.getId(); + public static final String AES_256_CBC = NISTObjectIdentifiers.id_aes256_CBC.getId(); + + public static final String DES3_CBC = PKCSObjectIdentifiers.des_EDE3_CBC.getId(); + + public static final String PBE_SHA1_RC4_128 = PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC4.getId(); + public static final String PBE_SHA1_RC4_40 = PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC4.getId(); + public static final String PBE_SHA1_3DES = PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC.getId(); + public static final String PBE_SHA1_2DES = PKCSObjectIdentifiers.pbeWithSHAAnd2_KeyTripleDES_CBC.getId(); + public static final String PBE_SHA1_RC2_128 = PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC2_CBC.getId(); + public static final String PBE_SHA1_RC2_40 = PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC2_CBC.getId(); + + private JcaJceHelper helper = new DefaultJcaJceHelper(); + + private AlgorithmParameters params; + private ASN1ObjectIdentifier algOID; + byte[] salt; + int iterationCount; + private Cipher cipher; + private SecureRandom random; + private AlgorithmParameterGenerator paramGen; + private SecretKeyFactory secKeyFact; + private char[] password; + + private SecretKey key; + + public JceOpenSSLPKCS8EncryptorBuilder(ASN1ObjectIdentifier algorithm) + { + algOID = algorithm; + + this.iterationCount = 2048; + } + + public JceOpenSSLPKCS8EncryptorBuilder setRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + public JceOpenSSLPKCS8EncryptorBuilder setPasssword(char[] password) + { + this.password = password; + + return this; + } + + public JceOpenSSLPKCS8EncryptorBuilder setIterationCount(int iterationCount) + { + this.iterationCount = iterationCount; + + return this; + } + + public JceOpenSSLPKCS8EncryptorBuilder setProvider(String providerName) + { + helper = new NamedJcaJceHelper(providerName); + + return this; + } + + public JceOpenSSLPKCS8EncryptorBuilder setProvider(Provider provider) + { + helper = new ProviderJcaJceHelper(provider); + + return this; + } + + public OutputEncryptor build() + throws OperatorCreationException + { + final AlgorithmIdentifier algID; + + salt = new byte[20]; + + if (random == null) + { + random = new SecureRandom(); + } + + random.nextBytes(salt); + + try + { + this.cipher = helper.createCipher(algOID.getId()); + + if (PEMUtilities.isPKCS5Scheme2(algOID)) + { + this.paramGen = helper.createAlgorithmParameterGenerator(algOID.getId()); + } + else + { + this.secKeyFact = helper.createSecretKeyFactory(algOID.getId()); + } + } + catch (GeneralSecurityException e) + { + throw new OperatorCreationException(algOID + " not available: " + e.getMessage(), e); + } + + if (PEMUtilities.isPKCS5Scheme2(algOID)) + { + params = paramGen.generateParameters(); + + try + { + KeyDerivationFunc scheme = new KeyDerivationFunc(algOID, ASN1Primitive.fromByteArray(params.getEncoded())); + KeyDerivationFunc func = new KeyDerivationFunc(PKCSObjectIdentifiers.id_PBKDF2, new PBKDF2Params(salt, iterationCount)); + + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(func); + v.add(scheme); + + algID = new AlgorithmIdentifier(PKCSObjectIdentifiers.id_PBES2, PBES2Parameters.getInstance(new DERSequence(v))); + } + catch (IOException e) + { + throw new OperatorCreationException(e.getMessage(), e); + } + + key = PEMUtilities.generateSecretKeyForPKCS5Scheme2(algOID.getId(), password, salt, iterationCount); + + try + { + cipher.init(Cipher.ENCRYPT_MODE, key, params); + } + catch (GeneralSecurityException e) + { + throw new OperatorCreationException(e.getMessage(), e); + } + } + else if (PEMUtilities.isPKCS12(algOID)) + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(new DEROctetString(salt)); + v.add(new ASN1Integer(iterationCount)); + + algID = new AlgorithmIdentifier(algOID, PKCS12PBEParams.getInstance(new DERSequence(v))); + + try + { + PBEKeySpec pbeSpec = new PBEKeySpec(password); + PBEParameterSpec defParams = new PBEParameterSpec(salt, iterationCount); + + key = secKeyFact.generateSecret(pbeSpec); + + cipher.init(Cipher.ENCRYPT_MODE, key, defParams); + } + catch (GeneralSecurityException e) + { + throw new OperatorCreationException(e.getMessage(), e); + } + } + else + { + throw new OperatorCreationException("unknown algorithm: " + algOID, null); + } + + return new OutputEncryptor() + { + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return algID; + } + + public OutputStream getOutputStream(OutputStream encOut) + { + return new CipherOutputStream(encOut, cipher); + } + + public GenericKey getKey() + { + return new JceGenericKey(algID, key); + } + }; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcePEMDecryptorProviderBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcePEMDecryptorProviderBuilder.java new file mode 100644 index 000000000..2a260f236 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcePEMDecryptorProviderBuilder.java @@ -0,0 +1,54 @@ +package org.spongycastle.openssl.jcajce; + +import java.security.Provider; + +import org.spongycastle.jcajce.DefaultJcaJceHelper; +import org.spongycastle.jcajce.JcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.ProviderJcaJceHelper; +import org.spongycastle.openssl.PEMDecryptor; +import org.spongycastle.openssl.PEMDecryptorProvider; +import org.spongycastle.openssl.PEMException; +import org.spongycastle.openssl.PasswordException; + +public class JcePEMDecryptorProviderBuilder +{ + private JcaJceHelper helper = new DefaultJcaJceHelper(); + + public JcePEMDecryptorProviderBuilder setProvider(Provider provider) + { + this.helper = new ProviderJcaJceHelper(provider); + + return this; + } + + public JcePEMDecryptorProviderBuilder setProvider(String providerName) + { + this.helper = new NamedJcaJceHelper(providerName); + + return this; + } + + public PEMDecryptorProvider build(final char[] password) + { + return new PEMDecryptorProvider() + { + public PEMDecryptor get(final String dekAlgName) + { + return new PEMDecryptor() + { + public byte[] decrypt(byte[] keyBytes, byte[] iv) + throws PEMException + { + if (password == null) + { + throw new PasswordException("Password is null, but a password is required"); + } + + return PEMUtilities.crypt(false, helper, keyBytes, password, dekAlgName, iv); + } + }; + } + }; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcePEMEncryptorBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcePEMEncryptorBuilder.java new file mode 100644 index 000000000..4a3f76477 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcePEMEncryptorBuilder.java @@ -0,0 +1,78 @@ +package org.spongycastle.openssl.jcajce; + +import java.security.Provider; +import java.security.SecureRandom; + +import org.spongycastle.jcajce.DefaultJcaJceHelper; +import org.spongycastle.jcajce.JcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.ProviderJcaJceHelper; +import org.spongycastle.openssl.PEMEncryptor; +import org.spongycastle.openssl.PEMException; + +public class JcePEMEncryptorBuilder +{ + private final String algorithm; + + private JcaJceHelper helper = new DefaultJcaJceHelper(); + private SecureRandom random; + + public JcePEMEncryptorBuilder(String algorithm) + { + this.algorithm = algorithm; + } + + public JcePEMEncryptorBuilder setProvider(Provider provider) + { + this.helper = new ProviderJcaJceHelper(provider); + + return this; + } + + public JcePEMEncryptorBuilder setProvider(String providerName) + { + this.helper = new NamedJcaJceHelper(providerName); + + return this; + } + + public JcePEMEncryptorBuilder setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + public PEMEncryptor build(final char[] password) + { + if (random == null) + { + random = new SecureRandom(); + } + + int ivLength = algorithm.startsWith("AES-") ? 16 : 8; + + final byte[] iv = new byte[ivLength]; + + random.nextBytes(iv); + + return new PEMEncryptor() + { + public String getAlgorithm() + { + return algorithm; + } + + public byte[] getIV() + { + return iv; + } + + public byte[] encrypt(byte[] encoding) + throws PEMException + { + return PEMUtilities.crypt(true, helper, encoding, password, algorithm, iv); + } + }; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/jcajce/PEMUtilities.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/jcajce/PEMUtilities.java new file mode 100644 index 000000000..050599dd8 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/openssl/jcajce/PEMUtilities.java @@ -0,0 +1,258 @@ +package org.spongycastle.openssl.jcajce; + +import java.security.Key; +import java.security.spec.AlgorithmParameterSpec; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.RC2ParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.DERObjectIdentifier; +import org.spongycastle.asn1.nist.NISTObjectIdentifiers; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.crypto.PBEParametersGenerator; +import org.spongycastle.crypto.generators.OpenSSLPBEParametersGenerator; +import org.spongycastle.crypto.generators.PKCS5S2ParametersGenerator; +import org.spongycastle.crypto.params.KeyParameter; +import org.spongycastle.jcajce.JcaJceHelper; +import org.spongycastle.openssl.EncryptionException; +import org.spongycastle.openssl.PEMException; +import org.spongycastle.util.Integers; + +class PEMUtilities +{ + private static final Map KEYSIZES = new HashMap(); + private static final Set PKCS5_SCHEME_1 = new HashSet(); + private static final Set PKCS5_SCHEME_2 = new HashSet(); + + static + { + PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithMD2AndDES_CBC); + PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithMD2AndRC2_CBC); + PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithMD5AndDES_CBC); + PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithMD5AndRC2_CBC); + PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithSHA1AndDES_CBC); + PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithSHA1AndRC2_CBC); + + PKCS5_SCHEME_2.add(PKCSObjectIdentifiers.id_PBES2); + PKCS5_SCHEME_2.add(PKCSObjectIdentifiers.des_EDE3_CBC); + PKCS5_SCHEME_2.add(NISTObjectIdentifiers.id_aes128_CBC); + PKCS5_SCHEME_2.add(NISTObjectIdentifiers.id_aes192_CBC); + PKCS5_SCHEME_2.add(NISTObjectIdentifiers.id_aes256_CBC); + + KEYSIZES.put(PKCSObjectIdentifiers.des_EDE3_CBC.getId(), Integers.valueOf(192)); + KEYSIZES.put(NISTObjectIdentifiers.id_aes128_CBC.getId(), Integers.valueOf(128)); + KEYSIZES.put(NISTObjectIdentifiers.id_aes192_CBC.getId(), Integers.valueOf(192)); + KEYSIZES.put(NISTObjectIdentifiers.id_aes256_CBC.getId(), Integers.valueOf(256)); + } + + static int getKeySize(String algorithm) + { + if (!KEYSIZES.containsKey(algorithm)) + { + throw new IllegalStateException("no key size for algorithm: " + algorithm); + } + + return ((Integer)KEYSIZES.get(algorithm)).intValue(); + } + + static boolean isPKCS5Scheme1(DERObjectIdentifier algOid) + { + return PKCS5_SCHEME_1.contains(algOid); + } + + static boolean isPKCS5Scheme2(ASN1ObjectIdentifier algOid) + { + return PKCS5_SCHEME_2.contains(algOid); + } + + public static boolean isPKCS12(DERObjectIdentifier algOid) + { + return algOid.getId().startsWith(PKCSObjectIdentifiers.pkcs_12PbeIds.getId()); + } + + public static SecretKey generateSecretKeyForPKCS5Scheme2(String algorithm, char[] password, byte[] salt, int iterationCount) + { + PBEParametersGenerator generator = new PKCS5S2ParametersGenerator(); + + generator.init( + PBEParametersGenerator.PKCS5PasswordToBytes(password), + salt, + iterationCount); + + return new SecretKeySpec(((KeyParameter)generator.generateDerivedParameters(PEMUtilities.getKeySize(algorithm))).getKey(), algorithm); + } + + static byte[] crypt( + boolean encrypt, + JcaJceHelper helper, + byte[] bytes, + char[] password, + String dekAlgName, + byte[] iv) + throws PEMException + { + AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv); + String alg; + String blockMode = "CBC"; + String padding = "PKCS5Padding"; + Key sKey; + + // Figure out block mode and padding. + if (dekAlgName.endsWith("-CFB")) + { + blockMode = "CFB"; + padding = "NoPadding"; + } + if (dekAlgName.endsWith("-ECB") || + "DES-EDE".equals(dekAlgName) || + "DES-EDE3".equals(dekAlgName)) + { + // ECB is actually the default (though seldom used) when OpenSSL + // uses DES-EDE (des2) or DES-EDE3 (des3). + blockMode = "ECB"; + paramSpec = null; + } + if (dekAlgName.endsWith("-OFB")) + { + blockMode = "OFB"; + padding = "NoPadding"; + } + + + // Figure out algorithm and key size. + if (dekAlgName.startsWith("DES-EDE")) + { + alg = "DESede"; + // "DES-EDE" is actually des2 in OpenSSL-speak! + // "DES-EDE3" is des3. + boolean des2 = !dekAlgName.startsWith("DES-EDE3"); + sKey = getKey(password, alg, 24, iv, des2); + } + else if (dekAlgName.startsWith("DES-")) + { + alg = "DES"; + sKey = getKey(password, alg, 8, iv); + } + else if (dekAlgName.startsWith("BF-")) + { + alg = "Blowfish"; + sKey = getKey(password, alg, 16, iv); + } + else if (dekAlgName.startsWith("RC2-")) + { + alg = "RC2"; + int keyBits = 128; + if (dekAlgName.startsWith("RC2-40-")) + { + keyBits = 40; + } + else if (dekAlgName.startsWith("RC2-64-")) + { + keyBits = 64; + } + sKey = getKey(password, alg, keyBits / 8, iv); + if (paramSpec == null) // ECB block mode + { + paramSpec = new RC2ParameterSpec(keyBits); + } + else + { + paramSpec = new RC2ParameterSpec(keyBits, iv); + } + } + else if (dekAlgName.startsWith("AES-")) + { + alg = "AES"; + byte[] salt = iv; + if (salt.length > 8) + { + salt = new byte[8]; + System.arraycopy(iv, 0, salt, 0, 8); + } + + int keyBits; + if (dekAlgName.startsWith("AES-128-")) + { + keyBits = 128; + } + else if (dekAlgName.startsWith("AES-192-")) + { + keyBits = 192; + } + else if (dekAlgName.startsWith("AES-256-")) + { + keyBits = 256; + } + else + { + throw new EncryptionException("unknown AES encryption with private key"); + } + sKey = getKey(password, "AES", keyBits / 8, salt); + } + else + { + throw new EncryptionException("unknown encryption with private key"); + } + + String transformation = alg + "/" + blockMode + "/" + padding; + + try + { + Cipher c = helper.createCipher(transformation); + int mode = encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE; + + if (paramSpec == null) // ECB block mode + { + c.init(mode, sKey); + } + else + { + c.init(mode, sKey, paramSpec); + } + return c.doFinal(bytes); + } + catch (Exception e) + { + throw new EncryptionException("exception using cipher - please check password and data.", e); + } + } + + private static SecretKey getKey( + char[] password, + String algorithm, + int keyLength, + byte[] salt) + { + return getKey(password, algorithm, keyLength, salt, false); + } + + private static SecretKey getKey( + char[] password, + String algorithm, + int keyLength, + byte[] salt, + boolean des2) + { + OpenSSLPBEParametersGenerator pGen = new OpenSSLPBEParametersGenerator(); + + pGen.init(PBEParametersGenerator.PKCS5PasswordToBytes(password), salt); + + KeyParameter keyParam; + keyParam = (KeyParameter) pGen.generateDerivedParameters(keyLength * 8); + byte[] key = keyParam.getKey(); + if (des2 && key.length >= 24) + { + // For DES2, we must copy first 8 bytes into the last 8 bytes. + System.arraycopy(key, 0, key, 16, 8); + } + return new SecretKeySpec(key, algorithm); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/AsymmetricKeyUnwrapper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/AsymmetricKeyUnwrapper.java new file mode 100644 index 000000000..2ed2f1450 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/AsymmetricKeyUnwrapper.java @@ -0,0 +1,19 @@ +package org.spongycastle.operator; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +public abstract class AsymmetricKeyUnwrapper + implements KeyUnwrapper +{ + private AlgorithmIdentifier algorithmId; + + protected AsymmetricKeyUnwrapper(AlgorithmIdentifier algorithmId) + { + this.algorithmId = algorithmId; + } + + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return algorithmId; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/AsymmetricKeyWrapper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/AsymmetricKeyWrapper.java new file mode 100644 index 000000000..3de802264 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/AsymmetricKeyWrapper.java @@ -0,0 +1,19 @@ +package org.spongycastle.operator; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +public abstract class AsymmetricKeyWrapper + implements KeyWrapper +{ + private AlgorithmIdentifier algorithmId; + + protected AsymmetricKeyWrapper(AlgorithmIdentifier algorithmId) + { + this.algorithmId = algorithmId; + } + + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return algorithmId; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/BufferingContentSigner.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/BufferingContentSigner.java new file mode 100644 index 000000000..e96a906c3 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/BufferingContentSigner.java @@ -0,0 +1,70 @@ +package org.spongycastle.operator; + +import java.io.OutputStream; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.util.io.BufferingOutputStream; + +/** + * A class that explicitly buffers the data to be signed, sending it in one + * block when ready for signing. + */ +public class BufferingContentSigner + implements ContentSigner +{ + private final ContentSigner contentSigner; + private final OutputStream output; + + /** + * Base constructor. + * + * @param contentSigner the content signer to be wrapped. + */ + public BufferingContentSigner(ContentSigner contentSigner) + { + this.contentSigner = contentSigner; + this.output = new BufferingOutputStream(contentSigner.getOutputStream()); + } + + /** + * Base constructor. + * + * @param contentSigner the content signer to be wrapped. + * @param bufferSize the size of the internal buffer to use. + */ + public BufferingContentSigner(ContentSigner contentSigner, int bufferSize) + { + this.contentSigner = contentSigner; + this.output = new BufferingOutputStream(contentSigner.getOutputStream(), bufferSize); + } + + /** + * Return the algorithm identifier supported by this signer. + * + * @return algorithm identifier for the signature generated. + */ + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return contentSigner.getAlgorithmIdentifier(); + } + + /** + * Return the buffering stream. + * + * @return the output stream used to accumulate the data. + */ + public OutputStream getOutputStream() + { + return output; + } + + /** + * Generate signature from internally buffered data. + * + * @return the signature calculated from the bytes written to the buffering stream. + */ + public byte[] getSignature() + { + return contentSigner.getSignature(); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/ContentSigner.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/ContentSigner.java new file mode 100644 index 000000000..fcdeefc45 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/ContentSigner.java @@ -0,0 +1,27 @@ +package org.spongycastle.operator; + +import java.io.OutputStream; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +public interface ContentSigner +{ + AlgorithmIdentifier getAlgorithmIdentifier(); + + /** + * Returns a stream that will accept data for the purpose of calculating + * a signature. Use org.spongycastle.util.io.TeeOutputStream if you want to accumulate + * the data on the fly as well. + * + * @return an OutputStream + */ + OutputStream getOutputStream(); + + /** + * Returns a signature based on the current data written to the stream, since the + * start or the last call to getSignature(). + * + * @return bytes representing the signature. + */ + byte[] getSignature(); +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/ContentVerifier.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/ContentVerifier.java new file mode 100644 index 000000000..a139ebb2e --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/ContentVerifier.java @@ -0,0 +1,31 @@ +package org.spongycastle.operator; + +import java.io.OutputStream; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +public interface ContentVerifier +{ + /** + * Return the algorithm identifier describing the signature + * algorithm and parameters this expander supports. + * + * @return algorithm oid and parameters. + */ + AlgorithmIdentifier getAlgorithmIdentifier(); + + /** + * Returns a stream that will accept data for the purpose of calculating + * a signature for later verification. Use org.spongycastle.util.io.TeeOutputStream if you want to accumulate + * the data on the fly as well. + * + * @return an OutputStream + */ + OutputStream getOutputStream(); + + /** + * @param expected expected value of the signature on the data. + * @return true if the signature verifies, false otherwise + */ + boolean verify(byte[] expected); +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/ContentVerifierProvider.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/ContentVerifierProvider.java new file mode 100644 index 000000000..9d91304a9 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/ContentVerifierProvider.java @@ -0,0 +1,34 @@ +package org.spongycastle.operator; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cert.X509CertificateHolder; + +/** + * General interface for providers of ContentVerifier objects. + */ +public interface ContentVerifierProvider +{ + /** + * Return whether or not this verifier has a certificate associated with it. + * + * @return true if there is an associated certificate, false otherwise. + */ + boolean hasAssociatedCertificate(); + + /** + * Return the associated certificate if there is one. + * + * @return a holder containing the associated certificate if there is one, null if there is not. + */ + X509CertificateHolder getAssociatedCertificate(); + + /** + * Return a ContentVerifier that matches the passed in algorithm identifier, + * + * @param verifierAlgorithmIdentifier the algorithm and parameters required. + * @return a matching ContentVerifier + * @throws OperatorCreationException if the required ContentVerifier cannot be created. + */ + ContentVerifier get(AlgorithmIdentifier verifierAlgorithmIdentifier) + throws OperatorCreationException; +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/DefaultDigestAlgorithmIdentifierFinder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/DefaultDigestAlgorithmIdentifierFinder.java new file mode 100644 index 000000000..42d6665ed --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/DefaultDigestAlgorithmIdentifierFinder.java @@ -0,0 +1,97 @@ +package org.spongycastle.operator; + +import java.util.HashMap; +import java.util.Map; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.DERNull; +import org.spongycastle.asn1.cryptopro.CryptoProObjectIdentifiers; +import org.spongycastle.asn1.nist.NISTObjectIdentifiers; +import org.spongycastle.asn1.oiw.OIWObjectIdentifiers; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.pkcs.RSASSAPSSparams; +import org.spongycastle.asn1.teletrust.TeleTrusTObjectIdentifiers; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x9.X9ObjectIdentifiers; + +public class DefaultDigestAlgorithmIdentifierFinder + implements DigestAlgorithmIdentifierFinder +{ + private static Map digestOids = new HashMap(); + private static Map digestNameToOids = new HashMap(); + + static + { + // + // digests + // + digestOids.put(OIWObjectIdentifiers.md4WithRSAEncryption, PKCSObjectIdentifiers.md4); + digestOids.put(OIWObjectIdentifiers.md4WithRSA, PKCSObjectIdentifiers.md4); + digestOids.put(OIWObjectIdentifiers.sha1WithRSA, OIWObjectIdentifiers.idSHA1); + + digestOids.put(PKCSObjectIdentifiers.sha224WithRSAEncryption, NISTObjectIdentifiers.id_sha224); + digestOids.put(PKCSObjectIdentifiers.sha256WithRSAEncryption, NISTObjectIdentifiers.id_sha256); + digestOids.put(PKCSObjectIdentifiers.sha384WithRSAEncryption, NISTObjectIdentifiers.id_sha384); + digestOids.put(PKCSObjectIdentifiers.sha512WithRSAEncryption, NISTObjectIdentifiers.id_sha512); + digestOids.put(PKCSObjectIdentifiers.md2WithRSAEncryption, PKCSObjectIdentifiers.md2); + digestOids.put(PKCSObjectIdentifiers.md4WithRSAEncryption, PKCSObjectIdentifiers.md4); + digestOids.put(PKCSObjectIdentifiers.md5WithRSAEncryption, PKCSObjectIdentifiers.md5); + digestOids.put(PKCSObjectIdentifiers.sha1WithRSAEncryption, OIWObjectIdentifiers.idSHA1); + + digestOids.put(X9ObjectIdentifiers.ecdsa_with_SHA1, OIWObjectIdentifiers.idSHA1); + digestOids.put(X9ObjectIdentifiers.ecdsa_with_SHA224, NISTObjectIdentifiers.id_sha224); + digestOids.put(X9ObjectIdentifiers.ecdsa_with_SHA256, NISTObjectIdentifiers.id_sha256); + digestOids.put(X9ObjectIdentifiers.ecdsa_with_SHA384, NISTObjectIdentifiers.id_sha384); + digestOids.put(X9ObjectIdentifiers.ecdsa_with_SHA512, NISTObjectIdentifiers.id_sha512); + digestOids.put(X9ObjectIdentifiers.id_dsa_with_sha1, OIWObjectIdentifiers.idSHA1); + + digestOids.put(NISTObjectIdentifiers.dsa_with_sha224, NISTObjectIdentifiers.id_sha224); + digestOids.put(NISTObjectIdentifiers.dsa_with_sha256, NISTObjectIdentifiers.id_sha256); + digestOids.put(NISTObjectIdentifiers.dsa_with_sha384, NISTObjectIdentifiers.id_sha384); + digestOids.put(NISTObjectIdentifiers.dsa_with_sha512, NISTObjectIdentifiers.id_sha512); + + digestOids.put(TeleTrusTObjectIdentifiers.rsaSignatureWithripemd128, TeleTrusTObjectIdentifiers.ripemd128); + digestOids.put(TeleTrusTObjectIdentifiers.rsaSignatureWithripemd160, TeleTrusTObjectIdentifiers.ripemd160); + digestOids.put(TeleTrusTObjectIdentifiers.rsaSignatureWithripemd256, TeleTrusTObjectIdentifiers.ripemd256); + + digestOids.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_94, CryptoProObjectIdentifiers.gostR3411); + digestOids.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_2001, CryptoProObjectIdentifiers.gostR3411); + + digestNameToOids.put("SHA-1", OIWObjectIdentifiers.idSHA1); + digestNameToOids.put("SHA-224", NISTObjectIdentifiers.id_sha224); + digestNameToOids.put("SHA-256", NISTObjectIdentifiers.id_sha256); + digestNameToOids.put("SHA-384", NISTObjectIdentifiers.id_sha384); + digestNameToOids.put("SHA-512", NISTObjectIdentifiers.id_sha512); + + digestNameToOids.put("GOST3411", CryptoProObjectIdentifiers.gostR3411); + + digestNameToOids.put("MD2", PKCSObjectIdentifiers.md2); + digestNameToOids.put("MD4", PKCSObjectIdentifiers.md4); + digestNameToOids.put("MD5", PKCSObjectIdentifiers.md5); + + digestNameToOids.put("RIPEMD128", TeleTrusTObjectIdentifiers.ripemd128); + digestNameToOids.put("RIPEMD160", TeleTrusTObjectIdentifiers.ripemd160); + digestNameToOids.put("RIPEMD256", TeleTrusTObjectIdentifiers.ripemd256); + } + + public AlgorithmIdentifier find(AlgorithmIdentifier sigAlgId) + { + AlgorithmIdentifier digAlgId; + + if (sigAlgId.getAlgorithm().equals(PKCSObjectIdentifiers.id_RSASSA_PSS)) + { + digAlgId = RSASSAPSSparams.getInstance(sigAlgId.getParameters()).getHashAlgorithm(); + } + else + { + digAlgId = new AlgorithmIdentifier((ASN1ObjectIdentifier)digestOids.get(sigAlgId.getAlgorithm()), DERNull.INSTANCE); + } + + return digAlgId; + } + + public AlgorithmIdentifier find(String digAlgName) + { + return new AlgorithmIdentifier((ASN1ObjectIdentifier)digestNameToOids.get(digAlgName), DERNull.INSTANCE); + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/DefaultSecretKeySizeProvider.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/DefaultSecretKeySizeProvider.java new file mode 100644 index 000000000..d830e5cc3 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/DefaultSecretKeySizeProvider.java @@ -0,0 +1,69 @@ +package org.spongycastle.operator; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.cryptopro.CryptoProObjectIdentifiers; +import org.spongycastle.asn1.nist.NISTObjectIdentifiers; +import org.spongycastle.asn1.ntt.NTTObjectIdentifiers; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.util.Integers; + +public class DefaultSecretKeySizeProvider + implements SecretKeySizeProvider +{ + public static final SecretKeySizeProvider INSTANCE = new DefaultSecretKeySizeProvider(); + + private static final Map KEY_SIZES; + + static + { + Map keySizes = new HashMap(); + + keySizes.put(new ASN1ObjectIdentifier("1.2.840.113533.7.66.10"), Integers.valueOf(128)); + + keySizes.put(PKCSObjectIdentifiers.des_EDE3_CBC, Integers.valueOf(192)); + + keySizes.put(NISTObjectIdentifiers.id_aes128_CBC, Integers.valueOf(128)); + keySizes.put(NISTObjectIdentifiers.id_aes192_CBC, Integers.valueOf(192)); + keySizes.put(NISTObjectIdentifiers.id_aes256_CBC, Integers.valueOf(256)); + + keySizes.put(NTTObjectIdentifiers.id_camellia128_cbc, Integers.valueOf(128)); + keySizes.put(NTTObjectIdentifiers.id_camellia192_cbc, Integers.valueOf(192)); + keySizes.put(NTTObjectIdentifiers.id_camellia256_cbc, Integers.valueOf(256)); + + keySizes.put(CryptoProObjectIdentifiers.gostR28147_gcfb, Integers.valueOf(256)); + + KEY_SIZES = Collections.unmodifiableMap(keySizes); + } + + public int getKeySize(AlgorithmIdentifier algorithmIdentifier) + { + int keySize = getKeySize(algorithmIdentifier.getAlgorithm()); + + // just need the OID + if (keySize > 0) + { + return keySize; + } + + // TODO: support OID/Parameter key sizes (e.g. RC2). + + return -1; + } + + public int getKeySize(ASN1ObjectIdentifier algorithm) + { + Integer keySize = (Integer)KEY_SIZES.get(algorithm); + + if (keySize != null) + { + return keySize.intValue(); + } + + return -1; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/DefaultSignatureAlgorithmIdentifierFinder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/DefaultSignatureAlgorithmIdentifierFinder.java new file mode 100644 index 000000000..cd70901c5 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/DefaultSignatureAlgorithmIdentifierFinder.java @@ -0,0 +1,212 @@ +package org.spongycastle.operator; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.DERNull; +import org.spongycastle.asn1.cryptopro.CryptoProObjectIdentifiers; +import org.spongycastle.asn1.nist.NISTObjectIdentifiers; +import org.spongycastle.asn1.oiw.OIWObjectIdentifiers; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.pkcs.RSASSAPSSparams; +import org.spongycastle.asn1.teletrust.TeleTrusTObjectIdentifiers; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x9.X9ObjectIdentifiers; +import org.spongycastle.util.Strings; + +public class DefaultSignatureAlgorithmIdentifierFinder + implements SignatureAlgorithmIdentifierFinder +{ + private static Map algorithms = new HashMap(); + private static Set noParams = new HashSet(); + private static Map params = new HashMap(); + private static Set pkcs15RsaEncryption = new HashSet(); + private static Map digestOids = new HashMap(); + + private static final ASN1ObjectIdentifier ENCRYPTION_RSA = PKCSObjectIdentifiers.rsaEncryption; + private static final ASN1ObjectIdentifier ENCRYPTION_DSA = X9ObjectIdentifiers.id_dsa_with_sha1; + private static final ASN1ObjectIdentifier ENCRYPTION_ECDSA = X9ObjectIdentifiers.ecdsa_with_SHA1; + private static final ASN1ObjectIdentifier ENCRYPTION_RSA_PSS = PKCSObjectIdentifiers.id_RSASSA_PSS; + private static final ASN1ObjectIdentifier ENCRYPTION_GOST3410 = CryptoProObjectIdentifiers.gostR3410_94; + private static final ASN1ObjectIdentifier ENCRYPTION_ECGOST3410 = CryptoProObjectIdentifiers.gostR3410_2001; + + static + { + algorithms.put("MD2WITHRSAENCRYPTION", PKCSObjectIdentifiers.md2WithRSAEncryption); + algorithms.put("MD2WITHRSA", PKCSObjectIdentifiers.md2WithRSAEncryption); + algorithms.put("MD5WITHRSAENCRYPTION", PKCSObjectIdentifiers.md5WithRSAEncryption); + algorithms.put("MD5WITHRSA", PKCSObjectIdentifiers.md5WithRSAEncryption); + algorithms.put("SHA1WITHRSAENCRYPTION", PKCSObjectIdentifiers.sha1WithRSAEncryption); + algorithms.put("SHA1WITHRSA", PKCSObjectIdentifiers.sha1WithRSAEncryption); + algorithms.put("SHA224WITHRSAENCRYPTION", PKCSObjectIdentifiers.sha224WithRSAEncryption); + algorithms.put("SHA224WITHRSA", PKCSObjectIdentifiers.sha224WithRSAEncryption); + algorithms.put("SHA256WITHRSAENCRYPTION", PKCSObjectIdentifiers.sha256WithRSAEncryption); + algorithms.put("SHA256WITHRSA", PKCSObjectIdentifiers.sha256WithRSAEncryption); + algorithms.put("SHA384WITHRSAENCRYPTION", PKCSObjectIdentifiers.sha384WithRSAEncryption); + algorithms.put("SHA384WITHRSA", PKCSObjectIdentifiers.sha384WithRSAEncryption); + algorithms.put("SHA512WITHRSAENCRYPTION", PKCSObjectIdentifiers.sha512WithRSAEncryption); + algorithms.put("SHA512WITHRSA", PKCSObjectIdentifiers.sha512WithRSAEncryption); + algorithms.put("SHA1WITHRSAANDMGF1", PKCSObjectIdentifiers.id_RSASSA_PSS); + algorithms.put("SHA224WITHRSAANDMGF1", PKCSObjectIdentifiers.id_RSASSA_PSS); + algorithms.put("SHA256WITHRSAANDMGF1", PKCSObjectIdentifiers.id_RSASSA_PSS); + algorithms.put("SHA384WITHRSAANDMGF1", PKCSObjectIdentifiers.id_RSASSA_PSS); + algorithms.put("SHA512WITHRSAANDMGF1", PKCSObjectIdentifiers.id_RSASSA_PSS); + algorithms.put("RIPEMD160WITHRSAENCRYPTION", TeleTrusTObjectIdentifiers.rsaSignatureWithripemd160); + algorithms.put("RIPEMD160WITHRSA", TeleTrusTObjectIdentifiers.rsaSignatureWithripemd160); + algorithms.put("RIPEMD128WITHRSAENCRYPTION", TeleTrusTObjectIdentifiers.rsaSignatureWithripemd128); + algorithms.put("RIPEMD128WITHRSA", TeleTrusTObjectIdentifiers.rsaSignatureWithripemd128); + algorithms.put("RIPEMD256WITHRSAENCRYPTION", TeleTrusTObjectIdentifiers.rsaSignatureWithripemd256); + algorithms.put("RIPEMD256WITHRSA", TeleTrusTObjectIdentifiers.rsaSignatureWithripemd256); + algorithms.put("SHA1WITHDSA", X9ObjectIdentifiers.id_dsa_with_sha1); + algorithms.put("DSAWITHSHA1", X9ObjectIdentifiers.id_dsa_with_sha1); + algorithms.put("SHA224WITHDSA", NISTObjectIdentifiers.dsa_with_sha224); + algorithms.put("SHA256WITHDSA", NISTObjectIdentifiers.dsa_with_sha256); + algorithms.put("SHA384WITHDSA", NISTObjectIdentifiers.dsa_with_sha384); + algorithms.put("SHA512WITHDSA", NISTObjectIdentifiers.dsa_with_sha512); + algorithms.put("SHA1WITHECDSA", X9ObjectIdentifiers.ecdsa_with_SHA1); + algorithms.put("ECDSAWITHSHA1", X9ObjectIdentifiers.ecdsa_with_SHA1); + algorithms.put("SHA224WITHECDSA", X9ObjectIdentifiers.ecdsa_with_SHA224); + algorithms.put("SHA256WITHECDSA", X9ObjectIdentifiers.ecdsa_with_SHA256); + algorithms.put("SHA384WITHECDSA", X9ObjectIdentifiers.ecdsa_with_SHA384); + algorithms.put("SHA512WITHECDSA", X9ObjectIdentifiers.ecdsa_with_SHA512); + algorithms.put("GOST3411WITHGOST3410", CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_94); + algorithms.put("GOST3411WITHGOST3410-94", CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_94); + algorithms.put("GOST3411WITHECGOST3410", CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_2001); + algorithms.put("GOST3411WITHECGOST3410-2001", CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_2001); + algorithms.put("GOST3411WITHGOST3410-2001", CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_2001); + + // + // According to RFC 3279, the ASN.1 encoding SHALL (id-dsa-with-sha1) or MUST (ecdsa-with-SHA*) omit the parameters field. + // The parameters field SHALL be NULL for RSA based signature algorithms. + // + noParams.add(X9ObjectIdentifiers.ecdsa_with_SHA1); + noParams.add(X9ObjectIdentifiers.ecdsa_with_SHA224); + noParams.add(X9ObjectIdentifiers.ecdsa_with_SHA256); + noParams.add(X9ObjectIdentifiers.ecdsa_with_SHA384); + noParams.add(X9ObjectIdentifiers.ecdsa_with_SHA512); + noParams.add(X9ObjectIdentifiers.id_dsa_with_sha1); + noParams.add(NISTObjectIdentifiers.dsa_with_sha224); + noParams.add(NISTObjectIdentifiers.dsa_with_sha256); + noParams.add(NISTObjectIdentifiers.dsa_with_sha384); + noParams.add(NISTObjectIdentifiers.dsa_with_sha512); + + // + // RFC 4491 + // + noParams.add(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_94); + noParams.add(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_2001); + + // + // PKCS 1.5 encrypted algorithms + // + pkcs15RsaEncryption.add(PKCSObjectIdentifiers.sha1WithRSAEncryption); + pkcs15RsaEncryption.add(PKCSObjectIdentifiers.sha224WithRSAEncryption); + pkcs15RsaEncryption.add(PKCSObjectIdentifiers.sha256WithRSAEncryption); + pkcs15RsaEncryption.add(PKCSObjectIdentifiers.sha384WithRSAEncryption); + pkcs15RsaEncryption.add(PKCSObjectIdentifiers.sha512WithRSAEncryption); + pkcs15RsaEncryption.add(TeleTrusTObjectIdentifiers.rsaSignatureWithripemd128); + pkcs15RsaEncryption.add(TeleTrusTObjectIdentifiers.rsaSignatureWithripemd160); + pkcs15RsaEncryption.add(TeleTrusTObjectIdentifiers.rsaSignatureWithripemd256); + + // + // explicit params + // + AlgorithmIdentifier sha1AlgId = new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, DERNull.INSTANCE); + params.put("SHA1WITHRSAANDMGF1", createPSSParams(sha1AlgId, 20)); + + AlgorithmIdentifier sha224AlgId = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha224, DERNull.INSTANCE); + params.put("SHA224WITHRSAANDMGF1", createPSSParams(sha224AlgId, 28)); + + AlgorithmIdentifier sha256AlgId = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256, DERNull.INSTANCE); + params.put("SHA256WITHRSAANDMGF1", createPSSParams(sha256AlgId, 32)); + + AlgorithmIdentifier sha384AlgId = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha384, DERNull.INSTANCE); + params.put("SHA384WITHRSAANDMGF1", createPSSParams(sha384AlgId, 48)); + + AlgorithmIdentifier sha512AlgId = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha512, DERNull.INSTANCE); + params.put("SHA512WITHRSAANDMGF1", createPSSParams(sha512AlgId, 64)); + + // + // digests + // + digestOids.put(PKCSObjectIdentifiers.sha224WithRSAEncryption, NISTObjectIdentifiers.id_sha224); + digestOids.put(PKCSObjectIdentifiers.sha256WithRSAEncryption, NISTObjectIdentifiers.id_sha256); + digestOids.put(PKCSObjectIdentifiers.sha384WithRSAEncryption, NISTObjectIdentifiers.id_sha384); + digestOids.put(PKCSObjectIdentifiers.sha512WithRSAEncryption, NISTObjectIdentifiers.id_sha512); + digestOids.put(PKCSObjectIdentifiers.md2WithRSAEncryption, PKCSObjectIdentifiers.md2); + digestOids.put(PKCSObjectIdentifiers.md4WithRSAEncryption, PKCSObjectIdentifiers.md4); + digestOids.put(PKCSObjectIdentifiers.md5WithRSAEncryption, PKCSObjectIdentifiers.md5); + digestOids.put(PKCSObjectIdentifiers.sha1WithRSAEncryption, OIWObjectIdentifiers.idSHA1); + digestOids.put(TeleTrusTObjectIdentifiers.rsaSignatureWithripemd128, TeleTrusTObjectIdentifiers.ripemd128); + digestOids.put(TeleTrusTObjectIdentifiers.rsaSignatureWithripemd160, TeleTrusTObjectIdentifiers.ripemd160); + digestOids.put(TeleTrusTObjectIdentifiers.rsaSignatureWithripemd256, TeleTrusTObjectIdentifiers.ripemd256); + digestOids.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_94, CryptoProObjectIdentifiers.gostR3411); + digestOids.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_2001, CryptoProObjectIdentifiers.gostR3411); + } + + private static AlgorithmIdentifier generate(String signatureAlgorithm) + { + AlgorithmIdentifier sigAlgId; + AlgorithmIdentifier encAlgId; + AlgorithmIdentifier digAlgId; + + String algorithmName = Strings.toUpperCase(signatureAlgorithm); + ASN1ObjectIdentifier sigOID = (ASN1ObjectIdentifier)algorithms.get(algorithmName); + if (sigOID == null) + { + throw new IllegalArgumentException("Unknown signature type requested: " + algorithmName); + } + + if (noParams.contains(sigOID)) + { + sigAlgId = new AlgorithmIdentifier(sigOID); + } + else if (params.containsKey(algorithmName)) + { + sigAlgId = new AlgorithmIdentifier(sigOID, (ASN1Encodable)params.get(algorithmName)); + } + else + { + sigAlgId = new AlgorithmIdentifier(sigOID, DERNull.INSTANCE); + } + + if (pkcs15RsaEncryption.contains(sigOID)) + { + encAlgId = new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE); + } + else + { + encAlgId = sigAlgId; + } + + if (sigAlgId.getAlgorithm().equals(PKCSObjectIdentifiers.id_RSASSA_PSS)) + { + digAlgId = ((RSASSAPSSparams)sigAlgId.getParameters()).getHashAlgorithm(); + } + else + { + digAlgId = new AlgorithmIdentifier((ASN1ObjectIdentifier)digestOids.get(sigOID), DERNull.INSTANCE); + } + + return sigAlgId; + } + + private static RSASSAPSSparams createPSSParams(AlgorithmIdentifier hashAlgId, int saltSize) + { + return new RSASSAPSSparams( + hashAlgId, + new AlgorithmIdentifier(PKCSObjectIdentifiers.id_mgf1, hashAlgId), + new ASN1Integer(saltSize), + new ASN1Integer(1)); + } + + public AlgorithmIdentifier find(String sigAlgName) + { + return generate(sigAlgName); + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/DigestAlgorithmIdentifierFinder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/DigestAlgorithmIdentifierFinder.java new file mode 100644 index 000000000..1254c38e3 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/DigestAlgorithmIdentifierFinder.java @@ -0,0 +1,24 @@ +package org.spongycastle.operator; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +public interface DigestAlgorithmIdentifierFinder +{ + /** + * Find the digest algorithm identifier that matches with + * the passed in signature algorithm identifier. + * + * @param sigAlgId the signature algorithm of interest. + * @return an algorithm identifier for the corresponding digest. + */ + AlgorithmIdentifier find(AlgorithmIdentifier sigAlgId); + + /** + * Find the algorithm identifier that matches with + * the passed in digest name. + * + * @param digAlgName the name of the digest algorithm of interest. + * @return an algorithm identifier for the digest signature. + */ + AlgorithmIdentifier find(String digAlgName); +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/DigestCalculator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/DigestCalculator.java new file mode 100644 index 000000000..0bb4712f5 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/DigestCalculator.java @@ -0,0 +1,36 @@ +package org.spongycastle.operator; + +import java.io.OutputStream; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +/** + * General interface for an operator that is able to calculate a digest from + * a stream of output. + */ +public interface DigestCalculator +{ + /** + * Return the algorithm identifier representing the digest implemented by + * this calculator. + * + * @return algorithm id and parameters. + */ + AlgorithmIdentifier getAlgorithmIdentifier(); + + /** + * Returns a stream that will accept data for the purpose of calculating + * a digest. Use org.spongycastle.util.io.TeeOutputStream if you want to accumulate + * the data on the fly as well. + * + * @return an OutputStream + */ + OutputStream getOutputStream(); + + /** + * Return the digest calculated on what has been written to the calculator's output stream. + * + * @return a digest. + */ + byte[] getDigest(); +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/DigestCalculatorProvider.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/DigestCalculatorProvider.java new file mode 100644 index 000000000..55a7c1437 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/DigestCalculatorProvider.java @@ -0,0 +1,9 @@ +package org.spongycastle.operator; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +public interface DigestCalculatorProvider +{ + DigestCalculator get(AlgorithmIdentifier digestAlgorithmIdentifier) + throws OperatorCreationException; +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/GenericKey.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/GenericKey.java new file mode 100644 index 000000000..5446ce7bc --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/GenericKey.java @@ -0,0 +1,41 @@ +package org.spongycastle.operator; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +public class GenericKey +{ + private AlgorithmIdentifier algorithmIdentifier; + private Object representation; + + /** + * @deprecated provide an AlgorithmIdentifier. + * @param representation key data + */ + public GenericKey(Object representation) + { + this.algorithmIdentifier = null; + this.representation = representation; + } + + public GenericKey(AlgorithmIdentifier algorithmIdentifier, byte[] representation) + { + this.algorithmIdentifier = algorithmIdentifier; + this.representation = representation; + } + + protected GenericKey(AlgorithmIdentifier algorithmIdentifier, Object representation) + { + this.algorithmIdentifier = algorithmIdentifier; + this.representation = representation; + } + + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return algorithmIdentifier; + } + + public Object getRepresentation() + { + return representation; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/InputDecryptor.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/InputDecryptor.java new file mode 100644 index 000000000..c55b3db05 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/InputDecryptor.java @@ -0,0 +1,29 @@ +package org.spongycastle.operator; + +import java.io.InputStream; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +/** + * General interface for an operator that is able to produce + * an InputStream that will decrypt a stream of encrypted data. + */ +public interface InputDecryptor +{ + /** + * Return the algorithm identifier describing the encryption + * algorithm and parameters this decryptor can process. + * + * @return algorithm oid and parameters. + */ + AlgorithmIdentifier getAlgorithmIdentifier(); + + /** + * Wrap the passed in input stream encIn, returning an input stream + * that decrypts what it reads from encIn before returning it. + * + * @param encIn InputStream containing encrypted input. + * @return an decrypting InputStream + */ + InputStream getInputStream(InputStream encIn); +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/InputDecryptorProvider.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/InputDecryptorProvider.java new file mode 100644 index 000000000..4ef7e9c05 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/InputDecryptorProvider.java @@ -0,0 +1,9 @@ +package org.spongycastle.operator; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +public interface InputDecryptorProvider +{ + public InputDecryptor get(AlgorithmIdentifier algorithm) + throws OperatorCreationException; +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/InputExpander.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/InputExpander.java new file mode 100644 index 000000000..870e48079 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/InputExpander.java @@ -0,0 +1,29 @@ +package org.spongycastle.operator; + +import java.io.InputStream; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +/** + * General interface for an operator that is able to produce + * an InputStream that will produce uncompressed data. + */ +public interface InputExpander +{ + /** + * Return the algorithm identifier describing the compression + * algorithm and parameters this expander supports. + * + * @return algorithm oid and parameters. + */ + AlgorithmIdentifier getAlgorithmIdentifier(); + + /** + * Wrap the passed in input stream comIn, returning an input stream + * that expands anything read in from comIn. + * + * @param comIn the compressed input data stream.. + * @return an expanding InputStream. + */ + InputStream getInputStream(InputStream comIn); +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/InputExpanderProvider.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/InputExpanderProvider.java new file mode 100644 index 000000000..d38b813ad --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/InputExpanderProvider.java @@ -0,0 +1,8 @@ +package org.spongycastle.operator; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +public interface InputExpanderProvider +{ + InputExpander get(AlgorithmIdentifier algorithm); +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/KeyUnwrapper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/KeyUnwrapper.java new file mode 100644 index 000000000..8e2162308 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/KeyUnwrapper.java @@ -0,0 +1,11 @@ +package org.spongycastle.operator; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +public interface KeyUnwrapper +{ + AlgorithmIdentifier getAlgorithmIdentifier(); + + GenericKey generateUnwrappedKey(AlgorithmIdentifier encryptionKeyAlgorithm, byte[] encryptedKey) + throws OperatorException; +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/KeyWrapper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/KeyWrapper.java new file mode 100644 index 000000000..4b7986df0 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/KeyWrapper.java @@ -0,0 +1,11 @@ +package org.spongycastle.operator; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +public interface KeyWrapper +{ + AlgorithmIdentifier getAlgorithmIdentifier(); + + byte[] generateWrappedKey(GenericKey encryptionKey) + throws OperatorException; +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/MacCalculator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/MacCalculator.java new file mode 100644 index 000000000..df59ed652 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/MacCalculator.java @@ -0,0 +1,34 @@ +package org.spongycastle.operator; + +import java.io.OutputStream; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +public interface MacCalculator +{ + AlgorithmIdentifier getAlgorithmIdentifier(); + + /** + * Returns a stream that will accept data for the purpose of calculating + * the MAC for later verification. Use org.spongycastle.util.io.TeeOutputStream if you want to accumulate + * the data on the fly as well. + * + * @return an OutputStream + */ + OutputStream getOutputStream(); + + /** + * Return the calculated MAC based on what has been written to the stream. + * + * @return calculated MAC. + */ + byte[] getMac(); + + + /** + * Return the key used for calculating the MAC. + * + * @return the MAC key. + */ + GenericKey getKey(); +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/MacCalculatorProvider.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/MacCalculatorProvider.java new file mode 100644 index 000000000..a30773f28 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/MacCalculatorProvider.java @@ -0,0 +1,8 @@ +package org.spongycastle.operator; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +public interface MacCalculatorProvider +{ + public MacCalculator get(AlgorithmIdentifier algorithm); +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/OperatorCreationException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/OperatorCreationException.java new file mode 100644 index 000000000..4e7cadacf --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/OperatorCreationException.java @@ -0,0 +1,15 @@ +package org.spongycastle.operator; + +public class OperatorCreationException + extends OperatorException +{ + public OperatorCreationException(String msg, Throwable cause) + { + super(msg, cause); + } + + public OperatorCreationException(String msg) + { + super(msg); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/OperatorException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/OperatorException.java new file mode 100644 index 000000000..32ce9e41c --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/OperatorException.java @@ -0,0 +1,24 @@ +package org.spongycastle.operator; + +public class OperatorException + extends Exception +{ + private Throwable cause; + + public OperatorException(String msg, Throwable cause) + { + super(msg); + + this.cause = cause; + } + + public OperatorException(String msg) + { + super(msg); + } + + public Throwable getCause() + { + return cause; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/OperatorStreamException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/OperatorStreamException.java new file mode 100644 index 000000000..960d292fe --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/OperatorStreamException.java @@ -0,0 +1,21 @@ +package org.spongycastle.operator; + +import java.io.IOException; + +public class OperatorStreamException + extends IOException +{ + private Throwable cause; + + public OperatorStreamException(String msg, Throwable cause) + { + super(msg); + + this.cause = cause; + } + + public Throwable getCause() + { + return cause; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/OutputCompressor.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/OutputCompressor.java new file mode 100644 index 000000000..0e10df9a5 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/OutputCompressor.java @@ -0,0 +1,29 @@ +package org.spongycastle.operator; + +import java.io.OutputStream; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +/** + * General interface for an operator that is able to produce + * an OutputStream that will output compressed data. + */ +public interface OutputCompressor +{ + /** + * Return the algorithm identifier describing the compression + * algorithm and parameters this compressor uses. + * + * @return algorithm oid and parameters. + */ + AlgorithmIdentifier getAlgorithmIdentifier(); + + /** + * Wrap the passed in output stream comOut, returning an output stream + * that compresses anything passed in before sending on to comOut. + * + * @param comOut output stream for compressed output. + * @return a compressing OutputStream + */ + OutputStream getOutputStream(OutputStream comOut); +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/OutputEncryptor.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/OutputEncryptor.java new file mode 100644 index 000000000..595e3b75e --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/OutputEncryptor.java @@ -0,0 +1,36 @@ +package org.spongycastle.operator; + +import java.io.OutputStream; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +/** + * General interface for an operator that is able to produce + * an OutputStream that will output encrypted data. + */ +public interface OutputEncryptor +{ + /** + * Return the algorithm identifier describing the encryption + * algorithm and parameters this encryptor uses. + * + * @return algorithm oid and parameters. + */ + AlgorithmIdentifier getAlgorithmIdentifier(); + + /** + * Wrap the passed in output stream encOut, returning an output stream + * that encrypts anything passed in before sending on to encOut. + * + * @param encOut output stream for encrypted output. + * @return an encrypting OutputStream + */ + OutputStream getOutputStream(OutputStream encOut); + + /** + * Return the key used for encrypting the output. + * + * @return the encryption key. + */ + GenericKey getKey(); +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/RawContentVerifier.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/RawContentVerifier.java new file mode 100644 index 000000000..56bfb47f9 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/RawContentVerifier.java @@ -0,0 +1,17 @@ +package org.spongycastle.operator; + +/** + * Interface for ContentVerifiers that also support raw signatures that can be + * verified using the digest of the calculated data. + */ +public interface RawContentVerifier +{ + /** + * Verify that the expected signature value was derived from the passed in digest. + * + * @param digest digest calculated from the content. + * @param expected expected value of the signature + * @return true if the expected signature is derived from the digest, false otherwise. + */ + boolean verify(byte[] digest, byte[] expected); +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/RuntimeOperatorException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/RuntimeOperatorException.java new file mode 100644 index 000000000..56cab04a7 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/RuntimeOperatorException.java @@ -0,0 +1,24 @@ +package org.spongycastle.operator; + +public class RuntimeOperatorException + extends RuntimeException +{ + private Throwable cause; + + public RuntimeOperatorException(String msg) + { + super(msg); + } + + public RuntimeOperatorException(String msg, Throwable cause) + { + super(msg); + + this.cause = cause; + } + + public Throwable getCause() + { + return cause; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/SecretKeySizeProvider.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/SecretKeySizeProvider.java new file mode 100644 index 000000000..cb2d6561a --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/SecretKeySizeProvider.java @@ -0,0 +1,17 @@ +package org.spongycastle.operator; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +public interface SecretKeySizeProvider +{ + int getKeySize(AlgorithmIdentifier algorithmIdentifier); + + /** + * Return the key size implied by the OID, if one exists. + * + * @param algorithm the OID of the algorithm of interest. + * @return -1 if there is no fixed key size associated with the OID, or more information is required. + */ + int getKeySize(ASN1ObjectIdentifier algorithm); +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/SignatureAlgorithmIdentifierFinder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/SignatureAlgorithmIdentifierFinder.java new file mode 100644 index 000000000..5c997bdaa --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/SignatureAlgorithmIdentifierFinder.java @@ -0,0 +1,15 @@ +package org.spongycastle.operator; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +public interface SignatureAlgorithmIdentifierFinder +{ + /** + * Find the signature algorithm identifier that matches with + * the passed in signature algorithm name. + * + * @param sigAlgName the name of the signature algorithm of interest. + * @return an algorithm identifier for the corresponding signature. + */ + AlgorithmIdentifier find(String sigAlgName); +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/SymmetricKeyUnwrapper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/SymmetricKeyUnwrapper.java new file mode 100644 index 000000000..705a76713 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/SymmetricKeyUnwrapper.java @@ -0,0 +1,19 @@ +package org.spongycastle.operator; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +public abstract class SymmetricKeyUnwrapper + implements KeyUnwrapper +{ + private AlgorithmIdentifier algorithmId; + + protected SymmetricKeyUnwrapper(AlgorithmIdentifier algorithmId) + { + this.algorithmId = algorithmId; + } + + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return algorithmId; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/SymmetricKeyWrapper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/SymmetricKeyWrapper.java new file mode 100644 index 000000000..56ac7ef1f --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/SymmetricKeyWrapper.java @@ -0,0 +1,19 @@ +package org.spongycastle.operator; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +public abstract class SymmetricKeyWrapper + implements KeyWrapper +{ + private AlgorithmIdentifier algorithmId; + + protected SymmetricKeyWrapper(AlgorithmIdentifier algorithmId) + { + this.algorithmId = algorithmId; + } + + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return algorithmId; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/AESUtil.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/AESUtil.java new file mode 100644 index 000000000..7abd31be2 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/AESUtil.java @@ -0,0 +1,34 @@ +package org.spongycastle.operator.bc; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.nist.NISTObjectIdentifiers; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.crypto.params.KeyParameter; + +class AESUtil +{ + static AlgorithmIdentifier determineKeyEncAlg(KeyParameter key) + { + int length = key.getKey().length * 8; + ASN1ObjectIdentifier wrapOid; + + if (length == 128) + { + wrapOid = NISTObjectIdentifiers.id_aes128_wrap; + } + else if (length == 192) + { + wrapOid = NISTObjectIdentifiers.id_aes192_wrap; + } + else if (length == 256) + { + wrapOid = NISTObjectIdentifiers.id_aes256_wrap; + } + else + { + throw new IllegalArgumentException("illegal keysize in AES"); + } + + return new AlgorithmIdentifier(wrapOid); // parameters absent + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcAESSymmetricKeyUnwrapper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcAESSymmetricKeyUnwrapper.java new file mode 100644 index 000000000..f9b8c09b0 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcAESSymmetricKeyUnwrapper.java @@ -0,0 +1,13 @@ +package org.spongycastle.operator.bc; + +import org.spongycastle.crypto.engines.AESWrapEngine; +import org.spongycastle.crypto.params.KeyParameter; + +public class BcAESSymmetricKeyUnwrapper + extends BcSymmetricKeyUnwrapper +{ + public BcAESSymmetricKeyUnwrapper(KeyParameter wrappingKey) + { + super(AESUtil.determineKeyEncAlg(wrappingKey), new AESWrapEngine(), wrappingKey); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcAESSymmetricKeyWrapper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcAESSymmetricKeyWrapper.java new file mode 100644 index 000000000..62dc062f6 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcAESSymmetricKeyWrapper.java @@ -0,0 +1,13 @@ +package org.spongycastle.operator.bc; + +import org.spongycastle.crypto.engines.AESWrapEngine; +import org.spongycastle.crypto.params.KeyParameter; + +public class BcAESSymmetricKeyWrapper + extends BcSymmetricKeyWrapper +{ + public BcAESSymmetricKeyWrapper(KeyParameter wrappingKey) + { + super(AESUtil.determineKeyEncAlg(wrappingKey), new AESWrapEngine(), wrappingKey); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcAsymmetricKeyUnwrapper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcAsymmetricKeyUnwrapper.java new file mode 100644 index 000000000..8fed9debe --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcAsymmetricKeyUnwrapper.java @@ -0,0 +1,51 @@ +package org.spongycastle.operator.bc; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.crypto.AsymmetricBlockCipher; +import org.spongycastle.crypto.InvalidCipherTextException; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.operator.AsymmetricKeyUnwrapper; +import org.spongycastle.operator.GenericKey; +import org.spongycastle.operator.OperatorException; + +public abstract class BcAsymmetricKeyUnwrapper + extends AsymmetricKeyUnwrapper +{ + private AsymmetricKeyParameter privateKey; + + public BcAsymmetricKeyUnwrapper(AlgorithmIdentifier encAlgId, AsymmetricKeyParameter privateKey) + { + super(encAlgId); + + this.privateKey = privateKey; + } + + public GenericKey generateUnwrappedKey(AlgorithmIdentifier encryptedKeyAlgorithm, byte[] encryptedKey) + throws OperatorException + { + AsymmetricBlockCipher keyCipher = createAsymmetricUnwrapper(this.getAlgorithmIdentifier().getAlgorithm()); + + keyCipher.init(false, privateKey); + try + { + byte[] key = keyCipher.processBlock(encryptedKey, 0, encryptedKey.length); + + if (encryptedKeyAlgorithm.getAlgorithm().equals(PKCSObjectIdentifiers.des_EDE3_CBC)) + { + return new GenericKey(encryptedKeyAlgorithm, key); + } + else + { + return new GenericKey(encryptedKeyAlgorithm, key); + } + } + catch (InvalidCipherTextException e) + { + throw new OperatorException("unable to recover secret key: " + e.getMessage(), e); + } + } + + protected abstract AsymmetricBlockCipher createAsymmetricUnwrapper(ASN1ObjectIdentifier algorithm); +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcAsymmetricKeyWrapper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcAsymmetricKeyWrapper.java new file mode 100644 index 000000000..8b5bb3e8c --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcAsymmetricKeyWrapper.java @@ -0,0 +1,60 @@ +package org.spongycastle.operator.bc; + +import java.security.SecureRandom; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.crypto.AsymmetricBlockCipher; +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.InvalidCipherTextException; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.crypto.params.ParametersWithRandom; +import org.spongycastle.operator.AsymmetricKeyWrapper; +import org.spongycastle.operator.GenericKey; +import org.spongycastle.operator.OperatorException; + +public abstract class BcAsymmetricKeyWrapper + extends AsymmetricKeyWrapper +{ + private AsymmetricKeyParameter publicKey; + private SecureRandom random; + + public BcAsymmetricKeyWrapper(AlgorithmIdentifier encAlgId, AsymmetricKeyParameter publicKey) + { + super(encAlgId); + + this.publicKey = publicKey; + } + + public BcAsymmetricKeyWrapper setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + public byte[] generateWrappedKey(GenericKey encryptionKey) + throws OperatorException + { + AsymmetricBlockCipher keyEncryptionCipher = createAsymmetricWrapper(getAlgorithmIdentifier().getAlgorithm()); + + CipherParameters params = publicKey; + if (random != null) + { + params = new ParametersWithRandom(params, random); + } + + try + { + byte[] keyEnc = OperatorUtils.getKeyBytes(encryptionKey); + keyEncryptionCipher.init(true, publicKey); + return keyEncryptionCipher.processBlock(keyEnc, 0, keyEnc.length); + } + catch (InvalidCipherTextException e) + { + throw new OperatorException("unable to encrypt contents key", e); + } + } + + protected abstract AsymmetricBlockCipher createAsymmetricWrapper(ASN1ObjectIdentifier algorithm); +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcContentSignerBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcContentSignerBuilder.java new file mode 100644 index 000000000..7160adff2 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcContentSignerBuilder.java @@ -0,0 +1,82 @@ +package org.spongycastle.operator.bc; + +import java.io.OutputStream; +import java.security.SecureRandom; +import java.util.Map; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.crypto.CryptoException; +import org.spongycastle.crypto.Signer; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.crypto.params.ParametersWithRandom; +import org.spongycastle.operator.ContentSigner; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.operator.RuntimeOperatorException; + +public abstract class BcContentSignerBuilder +{ + private SecureRandom random; + private AlgorithmIdentifier sigAlgId; + private AlgorithmIdentifier digAlgId; + + protected BcDigestProvider digestProvider; + + public BcContentSignerBuilder(AlgorithmIdentifier sigAlgId, AlgorithmIdentifier digAlgId) + { + this.sigAlgId = sigAlgId; + this.digAlgId = digAlgId; + this.digestProvider = BcDefaultDigestProvider.INSTANCE; + } + + public BcContentSignerBuilder setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + public ContentSigner build(AsymmetricKeyParameter privateKey) + throws OperatorCreationException + { + final Signer sig = createSigner(sigAlgId, digAlgId); + + if (random != null) + { + sig.init(true, new ParametersWithRandom(privateKey, random)); + } + else + { + sig.init(true, privateKey); + } + + return new ContentSigner() + { + private BcSignerOutputStream stream = new BcSignerOutputStream(sig); + + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return sigAlgId; + } + + public OutputStream getOutputStream() + { + return stream; + } + + public byte[] getSignature() + { + try + { + return stream.getSignature(); + } + catch (CryptoException e) + { + throw new RuntimeOperatorException("exception obtaining signature: " + e.getMessage(), e); + } + } + }; + } + + protected abstract Signer createSigner(AlgorithmIdentifier sigAlgId, AlgorithmIdentifier algorithmIdentifier) + throws OperatorCreationException; +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcContentVerifierProviderBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcContentVerifierProviderBuilder.java new file mode 100644 index 000000000..3b975e4fa --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcContentVerifierProviderBuilder.java @@ -0,0 +1,144 @@ +package org.spongycastle.operator.bc; + +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.crypto.Signer; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.operator.ContentVerifier; +import org.spongycastle.operator.ContentVerifierProvider; +import org.spongycastle.operator.OperatorCreationException; + +public abstract class BcContentVerifierProviderBuilder +{ + protected BcDigestProvider digestProvider; + + public BcContentVerifierProviderBuilder() + { + this.digestProvider = BcDefaultDigestProvider.INSTANCE; + } + + public ContentVerifierProvider build(final X509CertificateHolder certHolder) + throws OperatorCreationException + { + return new ContentVerifierProvider() + { + public boolean hasAssociatedCertificate() + { + return true; + } + + public X509CertificateHolder getAssociatedCertificate() + { + return certHolder; + } + + public ContentVerifier get(AlgorithmIdentifier algorithm) + throws OperatorCreationException + { + try + { + AsymmetricKeyParameter publicKey = extractKeyParameters(certHolder.getSubjectPublicKeyInfo()); + BcSignerOutputStream stream = createSignatureStream(algorithm, publicKey); + + return new SigVerifier(algorithm, stream); + } + catch (IOException e) + { + throw new OperatorCreationException("exception on setup: " + e, e); + } + } + }; + } + + public ContentVerifierProvider build(final AsymmetricKeyParameter publicKey) + throws OperatorCreationException + { + return new ContentVerifierProvider() + { + public boolean hasAssociatedCertificate() + { + return false; + } + + public X509CertificateHolder getAssociatedCertificate() + { + return null; + } + + public ContentVerifier get(AlgorithmIdentifier algorithm) + throws OperatorCreationException + { + BcSignerOutputStream stream = createSignatureStream(algorithm, publicKey); + + return new SigVerifier(algorithm, stream); + } + }; + } + + private BcSignerOutputStream createSignatureStream(AlgorithmIdentifier algorithm, AsymmetricKeyParameter publicKey) + throws OperatorCreationException + { + Signer sig = createSigner(algorithm); + + sig.init(false, publicKey); + + return new BcSignerOutputStream(sig); + } + + /** + * Extract an AsymmetricKeyParameter from the passed in SubjectPublicKeyInfo structure. + * + * @param publicKeyInfo a publicKeyInfo structure describing the public key required. + * @return an AsymmetricKeyParameter object containing the appropriate public key. + * @throws IOException if the publicKeyInfo data cannot be parsed, + */ + protected abstract AsymmetricKeyParameter extractKeyParameters(SubjectPublicKeyInfo publicKeyInfo) + throws IOException; + + /** + * Create the correct signer for the algorithm identifier sigAlgId. + * + * @param sigAlgId the algorithm details for the signature we want to verify. + * @return a Signer object. + * @throws OperatorCreationException if the Signer cannot be constructed. + */ + protected abstract Signer createSigner(AlgorithmIdentifier sigAlgId) + throws OperatorCreationException; + + private class SigVerifier + implements ContentVerifier + { + private BcSignerOutputStream stream; + private AlgorithmIdentifier algorithm; + + SigVerifier(AlgorithmIdentifier algorithm, BcSignerOutputStream stream) + { + this.algorithm = algorithm; + this.stream = stream; + } + + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return algorithm; + } + + public OutputStream getOutputStream() + { + if (stream == null) + { + throw new IllegalStateException("verifier not initialised"); + } + + return stream; + } + + public boolean verify(byte[] expected) + { + return stream.verify(expected); + } + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcDSAContentSignerBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcDSAContentSignerBuilder.java new file mode 100644 index 000000000..db7b608dc --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcDSAContentSignerBuilder.java @@ -0,0 +1,25 @@ +package org.spongycastle.operator.bc; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.Signer; +import org.spongycastle.crypto.signers.DSADigestSigner; +import org.spongycastle.crypto.signers.DSASigner; +import org.spongycastle.operator.OperatorCreationException; + +public class BcDSAContentSignerBuilder + extends BcContentSignerBuilder +{ + public BcDSAContentSignerBuilder(AlgorithmIdentifier sigAlgId, AlgorithmIdentifier digAlgId) + { + super(sigAlgId, digAlgId); + } + + protected Signer createSigner(AlgorithmIdentifier sigAlgId, AlgorithmIdentifier digAlgId) + throws OperatorCreationException + { + Digest dig = digestProvider.get(digAlgId); + + return new DSADigestSigner(new DSASigner(), dig); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcDSAContentVerifierProviderBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcDSAContentVerifierProviderBuilder.java new file mode 100644 index 000000000..aaf25f4d5 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcDSAContentVerifierProviderBuilder.java @@ -0,0 +1,40 @@ +package org.spongycastle.operator.bc; + +import java.io.IOException; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.Signer; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.crypto.signers.DSADigestSigner; +import org.spongycastle.crypto.signers.DSASigner; +import org.spongycastle.crypto.util.PublicKeyFactory; +import org.spongycastle.operator.DigestAlgorithmIdentifierFinder; +import org.spongycastle.operator.OperatorCreationException; + +public class BcDSAContentVerifierProviderBuilder + extends BcContentVerifierProviderBuilder +{ + private DigestAlgorithmIdentifierFinder digestAlgorithmFinder; + + public BcDSAContentVerifierProviderBuilder(DigestAlgorithmIdentifierFinder digestAlgorithmFinder) + { + this.digestAlgorithmFinder = digestAlgorithmFinder; + } + + protected Signer createSigner(AlgorithmIdentifier sigAlgId) + throws OperatorCreationException + { + AlgorithmIdentifier digAlg = digestAlgorithmFinder.find(sigAlgId); + Digest dig = digestProvider.get(digAlg); + + return new DSADigestSigner(new DSASigner(), dig); + } + + protected AsymmetricKeyParameter extractKeyParameters(SubjectPublicKeyInfo publicKeyInfo) + throws IOException + { + return PublicKeyFactory.createKey(publicKeyInfo); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcDefaultDigestProvider.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcDefaultDigestProvider.java new file mode 100644 index 000000000..dce50a9e2 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcDefaultDigestProvider.java @@ -0,0 +1,144 @@ +package org.spongycastle.operator.bc; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.spongycastle.asn1.cryptopro.CryptoProObjectIdentifiers; +import org.spongycastle.asn1.nist.NISTObjectIdentifiers; +import org.spongycastle.asn1.oiw.OIWObjectIdentifiers; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.teletrust.TeleTrusTObjectIdentifiers; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.crypto.ExtendedDigest; +import org.spongycastle.crypto.digests.GOST3411Digest; +import org.spongycastle.crypto.digests.MD2Digest; +import org.spongycastle.crypto.digests.MD4Digest; +import org.spongycastle.crypto.digests.MD5Digest; +import org.spongycastle.crypto.digests.RIPEMD128Digest; +import org.spongycastle.crypto.digests.RIPEMD160Digest; +import org.spongycastle.crypto.digests.RIPEMD256Digest; +import org.spongycastle.crypto.digests.SHA1Digest; +import org.spongycastle.crypto.digests.SHA224Digest; +import org.spongycastle.crypto.digests.SHA256Digest; +import org.spongycastle.crypto.digests.SHA384Digest; +import org.spongycastle.crypto.digests.SHA512Digest; +import org.spongycastle.operator.OperatorCreationException; + +public class BcDefaultDigestProvider + implements BcDigestProvider +{ + private static final Map lookup = createTable(); + + private static Map createTable() + { + Map table = new HashMap(); + + table.put(OIWObjectIdentifiers.idSHA1, new BcDigestProvider() + { + public ExtendedDigest get(AlgorithmIdentifier digestAlgorithmIdentifier) + { + return new SHA1Digest(); + } + }); + table.put(NISTObjectIdentifiers.id_sha224, new BcDigestProvider() + { + public ExtendedDigest get(AlgorithmIdentifier digestAlgorithmIdentifier) + { + return new SHA224Digest(); + } + }); + table.put(NISTObjectIdentifiers.id_sha256, new BcDigestProvider() + { + public ExtendedDigest get(AlgorithmIdentifier digestAlgorithmIdentifier) + { + return new SHA256Digest(); + } + }); + table.put(NISTObjectIdentifiers.id_sha384, new BcDigestProvider() + { + public ExtendedDigest get(AlgorithmIdentifier digestAlgorithmIdentifier) + { + return new SHA384Digest(); + } + }); + table.put(NISTObjectIdentifiers.id_sha512, new BcDigestProvider() + { + public ExtendedDigest get(AlgorithmIdentifier digestAlgorithmIdentifier) + { + return new SHA512Digest(); + } + }); + table.put(PKCSObjectIdentifiers.md5, new BcDigestProvider() + { + public ExtendedDigest get(AlgorithmIdentifier digestAlgorithmIdentifier) + { + return new MD5Digest(); + } + }); + table.put(PKCSObjectIdentifiers.md4, new BcDigestProvider() + { + public ExtendedDigest get(AlgorithmIdentifier digestAlgorithmIdentifier) + { + return new MD4Digest(); + } + }); + table.put(PKCSObjectIdentifiers.md2, new BcDigestProvider() + { + public ExtendedDigest get(AlgorithmIdentifier digestAlgorithmIdentifier) + { + return new MD2Digest(); + } + }); + table.put(CryptoProObjectIdentifiers.gostR3411, new BcDigestProvider() + { + public ExtendedDigest get(AlgorithmIdentifier digestAlgorithmIdentifier) + { + return new GOST3411Digest(); + } + }); + table.put(TeleTrusTObjectIdentifiers.ripemd128, new BcDigestProvider() + { + public ExtendedDigest get(AlgorithmIdentifier digestAlgorithmIdentifier) + { + return new RIPEMD128Digest(); + } + }); + table.put(TeleTrusTObjectIdentifiers.ripemd160, new BcDigestProvider() + { + public ExtendedDigest get(AlgorithmIdentifier digestAlgorithmIdentifier) + { + return new RIPEMD160Digest(); + } + }); + table.put(TeleTrusTObjectIdentifiers.ripemd256, new BcDigestProvider() + { + public ExtendedDigest get(AlgorithmIdentifier digestAlgorithmIdentifier) + { + return new RIPEMD256Digest(); + } + }); + + return Collections.unmodifiableMap(table); + } + + public static final BcDigestProvider INSTANCE = new BcDefaultDigestProvider(); + + private BcDefaultDigestProvider() + { + + } + + public ExtendedDigest get(AlgorithmIdentifier digestAlgorithmIdentifier) + throws OperatorCreationException + { + BcDigestProvider extProv = (BcDigestProvider)lookup.get(digestAlgorithmIdentifier.getAlgorithm()); + + if (extProv == null) + { + throw new OperatorCreationException("cannot recognise digest"); + } + + return extProv.get(digestAlgorithmIdentifier); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcDigestCalculatorProvider.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcDigestCalculatorProvider.java new file mode 100644 index 000000000..8e0a12f64 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcDigestCalculatorProvider.java @@ -0,0 +1,82 @@ +package org.spongycastle.operator.bc; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Map; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.ExtendedDigest; +import org.spongycastle.operator.DigestCalculator; +import org.spongycastle.operator.DigestCalculatorProvider; +import org.spongycastle.operator.OperatorCreationException; + +public class BcDigestCalculatorProvider + implements DigestCalculatorProvider +{ + private BcDigestProvider digestProvider = BcDefaultDigestProvider.INSTANCE; + + public DigestCalculator get(final AlgorithmIdentifier algorithm) + throws OperatorCreationException + { + Digest dig = digestProvider.get(algorithm); + + final DigestOutputStream stream = new DigestOutputStream(dig); + + return new DigestCalculator() + { + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return algorithm; + } + + public OutputStream getOutputStream() + { + return stream; + } + + public byte[] getDigest() + { + return stream.getDigest(); + } + }; + } + + private class DigestOutputStream + extends OutputStream + { + private Digest dig; + + DigestOutputStream(Digest dig) + { + this.dig = dig; + } + + public void write(byte[] bytes, int off, int len) + throws IOException + { + dig.update(bytes, off, len); + } + + public void write(byte[] bytes) + throws IOException + { + dig.update(bytes, 0, bytes.length); + } + + public void write(int b) + throws IOException + { + dig.update((byte)b); + } + + byte[] getDigest() + { + byte[] d = new byte[dig.getDigestSize()]; + + dig.doFinal(d, 0); + + return d; + } + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcDigestProvider.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcDigestProvider.java new file mode 100644 index 000000000..6eb930ee5 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcDigestProvider.java @@ -0,0 +1,11 @@ +package org.spongycastle.operator.bc; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.crypto.ExtendedDigest; +import org.spongycastle.operator.OperatorCreationException; + +public interface BcDigestProvider +{ + ExtendedDigest get(AlgorithmIdentifier digestAlgorithmIdentifier) + throws OperatorCreationException; +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcRSAAsymmetricKeyUnwrapper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcRSAAsymmetricKeyUnwrapper.java new file mode 100644 index 000000000..997623534 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcRSAAsymmetricKeyUnwrapper.java @@ -0,0 +1,22 @@ +package org.spongycastle.operator.bc; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.crypto.AsymmetricBlockCipher; +import org.spongycastle.crypto.encodings.PKCS1Encoding; +import org.spongycastle.crypto.engines.RSAEngine; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; + +public class BcRSAAsymmetricKeyUnwrapper + extends BcAsymmetricKeyUnwrapper +{ + public BcRSAAsymmetricKeyUnwrapper(AlgorithmIdentifier encAlgId, AsymmetricKeyParameter privateKey) + { + super(encAlgId, privateKey); + } + + protected AsymmetricBlockCipher createAsymmetricUnwrapper(ASN1ObjectIdentifier algorithm) + { + return new PKCS1Encoding(new RSAEngine()); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcRSAAsymmetricKeyWrapper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcRSAAsymmetricKeyWrapper.java new file mode 100644 index 000000000..c2153e6d3 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcRSAAsymmetricKeyWrapper.java @@ -0,0 +1,32 @@ +package org.spongycastle.operator.bc; + +import java.io.IOException; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.crypto.AsymmetricBlockCipher; +import org.spongycastle.crypto.encodings.PKCS1Encoding; +import org.spongycastle.crypto.engines.RSAEngine; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.crypto.util.PublicKeyFactory; + +public class BcRSAAsymmetricKeyWrapper + extends BcAsymmetricKeyWrapper +{ + public BcRSAAsymmetricKeyWrapper(AlgorithmIdentifier encAlgId, AsymmetricKeyParameter publicKey) + { + super(encAlgId, publicKey); + } + + public BcRSAAsymmetricKeyWrapper(AlgorithmIdentifier encAlgId, SubjectPublicKeyInfo publicKeyInfo) + throws IOException + { + super(encAlgId, PublicKeyFactory.createKey(publicKeyInfo)); + } + + protected AsymmetricBlockCipher createAsymmetricWrapper(ASN1ObjectIdentifier algorithm) + { + return new PKCS1Encoding(new RSAEngine()); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcRSAContentSignerBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcRSAContentSignerBuilder.java new file mode 100644 index 000000000..d62543b00 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcRSAContentSignerBuilder.java @@ -0,0 +1,24 @@ +package org.spongycastle.operator.bc; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.Signer; +import org.spongycastle.crypto.signers.RSADigestSigner; +import org.spongycastle.operator.OperatorCreationException; + +public class BcRSAContentSignerBuilder + extends BcContentSignerBuilder +{ + public BcRSAContentSignerBuilder(AlgorithmIdentifier sigAlgId, AlgorithmIdentifier digAlgId) + { + super(sigAlgId, digAlgId); + } + + protected Signer createSigner(AlgorithmIdentifier sigAlgId, AlgorithmIdentifier digAlgId) + throws OperatorCreationException + { + Digest dig = digestProvider.get(digAlgId); + + return new RSADigestSigner(dig); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcRSAContentVerifierProviderBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcRSAContentVerifierProviderBuilder.java new file mode 100644 index 000000000..e1fd6736d --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcRSAContentVerifierProviderBuilder.java @@ -0,0 +1,39 @@ +package org.spongycastle.operator.bc; + +import java.io.IOException; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.Signer; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.crypto.signers.RSADigestSigner; +import org.spongycastle.crypto.util.PublicKeyFactory; +import org.spongycastle.operator.DigestAlgorithmIdentifierFinder; +import org.spongycastle.operator.OperatorCreationException; + +public class BcRSAContentVerifierProviderBuilder + extends BcContentVerifierProviderBuilder +{ + private DigestAlgorithmIdentifierFinder digestAlgorithmFinder; + + public BcRSAContentVerifierProviderBuilder(DigestAlgorithmIdentifierFinder digestAlgorithmFinder) + { + this.digestAlgorithmFinder = digestAlgorithmFinder; + } + + protected Signer createSigner(AlgorithmIdentifier sigAlgId) + throws OperatorCreationException + { + AlgorithmIdentifier digAlg = digestAlgorithmFinder.find(sigAlgId); + Digest dig = digestProvider.get(digAlg); + + return new RSADigestSigner(dig); + } + + protected AsymmetricKeyParameter extractKeyParameters(SubjectPublicKeyInfo publicKeyInfo) + throws IOException + { + return PublicKeyFactory.createKey(publicKeyInfo); + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcSignerOutputStream.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcSignerOutputStream.java new file mode 100644 index 000000000..f4cdf62b9 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcSignerOutputStream.java @@ -0,0 +1,47 @@ +package org.spongycastle.operator.bc; + +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.crypto.CryptoException; +import org.spongycastle.crypto.Signer; + +public class BcSignerOutputStream + extends OutputStream +{ + private Signer sig; + + BcSignerOutputStream(Signer sig) + { + this.sig = sig; + } + + public void write(byte[] bytes, int off, int len) + throws IOException + { + sig.update(bytes, off, len); + } + + public void write(byte[] bytes) + throws IOException + { + sig.update(bytes, 0, bytes.length); + } + + public void write(int b) + throws IOException + { + sig.update((byte)b); + } + + byte[] getSignature() + throws CryptoException + { + return sig.generateSignature(); + } + + boolean verify(byte[] expected) + { + return sig.verifySignature(expected); + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcSymmetricKeyUnwrapper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcSymmetricKeyUnwrapper.java new file mode 100644 index 000000000..da37cf1aa --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcSymmetricKeyUnwrapper.java @@ -0,0 +1,49 @@ +package org.spongycastle.operator.bc; + +import java.security.SecureRandom; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.crypto.InvalidCipherTextException; +import org.spongycastle.crypto.Wrapper; +import org.spongycastle.crypto.params.KeyParameter; +import org.spongycastle.operator.GenericKey; +import org.spongycastle.operator.OperatorException; +import org.spongycastle.operator.SymmetricKeyUnwrapper; + +public class BcSymmetricKeyUnwrapper + extends SymmetricKeyUnwrapper +{ + private SecureRandom random; + private Wrapper wrapper; + private KeyParameter wrappingKey; + + public BcSymmetricKeyUnwrapper(AlgorithmIdentifier wrappingAlgorithm, Wrapper wrapper, KeyParameter wrappingKey) + { + super(wrappingAlgorithm); + + this.wrapper = wrapper; + this.wrappingKey = wrappingKey; + } + + public BcSymmetricKeyUnwrapper setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + public GenericKey generateUnwrappedKey(AlgorithmIdentifier encryptedKeyAlgorithm, byte[] encryptedKey) + throws OperatorException + { + wrapper.init(false, wrappingKey); + + try + { + return new GenericKey(encryptedKeyAlgorithm, wrapper.unwrap(encryptedKey, 0, encryptedKey.length)); + } + catch (InvalidCipherTextException e) + { + throw new OperatorException("unable to unwrap key: " + e.getMessage(), e); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcSymmetricKeyWrapper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcSymmetricKeyWrapper.java new file mode 100644 index 000000000..a35de7d71 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/BcSymmetricKeyWrapper.java @@ -0,0 +1,51 @@ +package org.spongycastle.operator.bc; + +import java.security.SecureRandom; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.crypto.Wrapper; +import org.spongycastle.crypto.params.KeyParameter; +import org.spongycastle.crypto.params.ParametersWithRandom; +import org.spongycastle.operator.GenericKey; +import org.spongycastle.operator.OperatorException; +import org.spongycastle.operator.SymmetricKeyWrapper; + +public class BcSymmetricKeyWrapper + extends SymmetricKeyWrapper +{ + private SecureRandom random; + private Wrapper wrapper; + private KeyParameter wrappingKey; + + public BcSymmetricKeyWrapper(AlgorithmIdentifier wrappingAlgorithm, Wrapper wrapper, KeyParameter wrappingKey) + { + super(wrappingAlgorithm); + + this.wrapper = wrapper; + this.wrappingKey = wrappingKey; + } + + public BcSymmetricKeyWrapper setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + public byte[] generateWrappedKey(GenericKey encryptionKey) + throws OperatorException + { + byte[] contentEncryptionKeySpec = OperatorUtils.getKeyBytes(encryptionKey); + + if (random == null) + { + wrapper.init(true, wrappingKey); + } + else + { + wrapper.init(true, new ParametersWithRandom(wrappingKey, random)); + } + + return wrapper.wrap(contentEncryptionKeySpec, 0, contentEncryptionKeySpec.length); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/CamelliaUtil.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/CamelliaUtil.java new file mode 100644 index 000000000..9b7c9836b --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/CamelliaUtil.java @@ -0,0 +1,36 @@ +package org.spongycastle.operator.bc; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ntt.NTTObjectIdentifiers; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.crypto.params.KeyParameter; + +class CamelliaUtil +{ + static AlgorithmIdentifier determineKeyEncAlg(KeyParameter key) + { + int length = key.getKey().length * 8; + ASN1ObjectIdentifier wrapOid; + + if (length == 128) + { + wrapOid = NTTObjectIdentifiers.id_camellia128_wrap; + } + else if (length == 192) + { + wrapOid = NTTObjectIdentifiers.id_camellia192_wrap; + } + else if (length == 256) + { + wrapOid = NTTObjectIdentifiers.id_camellia256_wrap; + } + else + { + throw new IllegalArgumentException( + "illegal keysize in Camellia"); + } + + return new AlgorithmIdentifier(wrapOid); // parameters must be + // absent + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/OperatorUtils.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/OperatorUtils.java new file mode 100644 index 000000000..a53c46923 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/OperatorUtils.java @@ -0,0 +1,23 @@ +package org.spongycastle.operator.bc; + +import java.security.Key; + +import org.spongycastle.operator.GenericKey; + +class OperatorUtils +{ + static byte[] getKeyBytes(GenericKey key) + { + if (key.getRepresentation() instanceof Key) + { + return ((Key)key.getRepresentation()).getEncoded(); + } + + if (key.getRepresentation() instanceof byte[]) + { + return (byte[])key.getRepresentation(); + } + + throw new IllegalArgumentException("unknown generic key type"); + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/SEEDUtil.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/SEEDUtil.java new file mode 100644 index 000000000..4d9773100 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/bc/SEEDUtil.java @@ -0,0 +1,14 @@ +package org.spongycastle.operator.bc; + +import org.spongycastle.asn1.kisa.KISAObjectIdentifiers; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; + +class SEEDUtil +{ + static AlgorithmIdentifier determineKeyEncAlg() + { + // parameters absent + return new AlgorithmIdentifier( + KISAObjectIdentifiers.id_npki_app_cmsSeed_wrap); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/JcaAlgorithmParametersConverter.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/JcaAlgorithmParametersConverter.java new file mode 100644 index 000000000..98e2b929b --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/JcaAlgorithmParametersConverter.java @@ -0,0 +1,73 @@ +package org.spongycastle.operator.jcajce; + + +import java.io.IOException; +import java.security.AlgorithmParameters; +import java.security.InvalidAlgorithmParameterException; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.MGF1ParameterSpec; + +import javax.crypto.spec.OAEPParameterSpec; +import javax.crypto.spec.PSource; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.pkcs.RSAESOAEPparams; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.operator.DefaultDigestAlgorithmIdentifierFinder; + +public class JcaAlgorithmParametersConverter +{ + public JcaAlgorithmParametersConverter() + { + } + + public AlgorithmIdentifier getAlgorithmIdentifier(ASN1ObjectIdentifier algId, AlgorithmParameters parameters) + throws InvalidAlgorithmParameterException + { + try + { + ASN1Encodable params = ASN1Primitive.fromByteArray(parameters.getEncoded()); + + return new AlgorithmIdentifier(algId, params); + } + catch (IOException e) + { + throw new InvalidAlgorithmParameterException("unable to encode parameters object: " + e.getMessage()); + } + } + + public AlgorithmIdentifier getAlgorithmIdentifier(ASN1ObjectIdentifier algorithm, AlgorithmParameterSpec algorithmSpec) + throws InvalidAlgorithmParameterException + { + if (algorithmSpec instanceof OAEPParameterSpec) + { + if (algorithmSpec.equals(OAEPParameterSpec.DEFAULT)) + { + return new AlgorithmIdentifier(algorithm, + new RSAESOAEPparams(RSAESOAEPparams.DEFAULT_HASH_ALGORITHM, RSAESOAEPparams.DEFAULT_MASK_GEN_FUNCTION, RSAESOAEPparams.DEFAULT_P_SOURCE_ALGORITHM)); + } + else + { + OAEPParameterSpec oaepSpec = (OAEPParameterSpec)algorithmSpec; + PSource pSource = oaepSpec.getPSource(); + + if (!oaepSpec.getMGFAlgorithm().equals(OAEPParameterSpec.DEFAULT.getMGFAlgorithm())) + { + throw new InvalidAlgorithmParameterException("only " + OAEPParameterSpec.DEFAULT.getMGFAlgorithm() + " mask generator supported."); + } + + AlgorithmIdentifier hashAlgorithm = new DefaultDigestAlgorithmIdentifierFinder().find(oaepSpec.getDigestAlgorithm()); + AlgorithmIdentifier mgf1HashAlgorithm = new DefaultDigestAlgorithmIdentifierFinder().find((((MGF1ParameterSpec)oaepSpec.getMGFParameters()).getDigestAlgorithm())); + return new AlgorithmIdentifier(algorithm, + new RSAESOAEPparams(hashAlgorithm, new AlgorithmIdentifier(PKCSObjectIdentifiers.id_mgf1, mgf1HashAlgorithm), + new AlgorithmIdentifier(PKCSObjectIdentifiers.id_pSpecified, new DEROctetString(((PSource.PSpecified)pSource).getValue())))); + } + } + + throw new InvalidAlgorithmParameterException("unknown parameter spec passed."); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/JcaContentSignerBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/JcaContentSignerBuilder.java new file mode 100644 index 000000000..a796bbb89 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/JcaContentSignerBuilder.java @@ -0,0 +1,160 @@ +package org.spongycastle.operator.jcajce; + +import java.io.IOException; +import java.io.OutputStream; +import java.security.GeneralSecurityException; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.SecureRandom; +import java.security.Signature; +import java.security.SignatureException; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.jcajce.DefaultJcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.ProviderJcaJceHelper; +import org.spongycastle.operator.ContentSigner; +import org.spongycastle.operator.DefaultSignatureAlgorithmIdentifierFinder; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.operator.OperatorStreamException; +import org.spongycastle.operator.RuntimeOperatorException; + +public class JcaContentSignerBuilder +{ + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + private SecureRandom random; + private String signatureAlgorithm; + private AlgorithmIdentifier sigAlgId; + + public JcaContentSignerBuilder(String signatureAlgorithm) + { + this.signatureAlgorithm = signatureAlgorithm; + this.sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find(signatureAlgorithm); + } + + public JcaContentSignerBuilder setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + + return this; + } + + public JcaContentSignerBuilder setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + + return this; + } + + public JcaContentSignerBuilder setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + public ContentSigner build(PrivateKey privateKey) + throws OperatorCreationException + { + try + { + final Signature sig = helper.createSignature(sigAlgId); + + if (random != null) + { + sig.initSign(privateKey, random); + } + else + { + sig.initSign(privateKey); + } + + return new ContentSigner() + { + private SignatureOutputStream stream = new SignatureOutputStream(sig); + + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return sigAlgId; + } + + public OutputStream getOutputStream() + { + return stream; + } + + public byte[] getSignature() + { + try + { + return stream.getSignature(); + } + catch (SignatureException e) + { + throw new RuntimeOperatorException("exception obtaining signature: " + e.getMessage(), e); + } + } + }; + } + catch (GeneralSecurityException e) + { + throw new OperatorCreationException("cannot create signer: " + e.getMessage(), e); + } + } + + private class SignatureOutputStream + extends OutputStream + { + private Signature sig; + + SignatureOutputStream(Signature sig) + { + this.sig = sig; + } + + public void write(byte[] bytes, int off, int len) + throws IOException + { + try + { + sig.update(bytes, off, len); + } + catch (SignatureException e) + { + throw new OperatorStreamException("exception in content signer: " + e.getMessage(), e); + } + } + + public void write(byte[] bytes) + throws IOException + { + try + { + sig.update(bytes); + } + catch (SignatureException e) + { + throw new OperatorStreamException("exception in content signer: " + e.getMessage(), e); + } + } + + public void write(int b) + throws IOException + { + try + { + sig.update((byte)b); + } + catch (SignatureException e) + { + throw new OperatorStreamException("exception in content signer: " + e.getMessage(), e); + } + } + + byte[] getSignature() + throws SignatureException + { + return sig.sign(); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/JcaContentVerifierProviderBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/JcaContentVerifierProviderBuilder.java new file mode 100644 index 000000000..5db293186 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/JcaContentVerifierProviderBuilder.java @@ -0,0 +1,312 @@ +package org.spongycastle.operator.jcajce; + +import java.io.IOException; +import java.io.OutputStream; +import java.security.GeneralSecurityException; +import java.security.Provider; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.cert.jcajce.JcaX509CertificateHolder; +import org.spongycastle.jcajce.DefaultJcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.ProviderJcaJceHelper; +import org.spongycastle.operator.ContentVerifier; +import org.spongycastle.operator.ContentVerifierProvider; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.operator.OperatorStreamException; +import org.spongycastle.operator.RawContentVerifier; +import org.spongycastle.operator.RuntimeOperatorException; + +public class JcaContentVerifierProviderBuilder +{ + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + + public JcaContentVerifierProviderBuilder() + { + } + + public JcaContentVerifierProviderBuilder setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + + return this; + } + + public JcaContentVerifierProviderBuilder setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + + return this; + } + + public ContentVerifierProvider build(X509CertificateHolder certHolder) + throws OperatorCreationException, CertificateException + { + return build(helper.convertCertificate(certHolder)); + } + + public ContentVerifierProvider build(final X509Certificate certificate) + throws OperatorCreationException + { + final X509CertificateHolder certHolder; + + try + { + certHolder = new JcaX509CertificateHolder(certificate); + } + catch (CertificateEncodingException e) + { + throw new OperatorCreationException("cannot process certificate: " + e.getMessage(), e); + } + + return new ContentVerifierProvider() + { + private SignatureOutputStream stream; + + public boolean hasAssociatedCertificate() + { + return true; + } + + public X509CertificateHolder getAssociatedCertificate() + { + return certHolder; + } + + public ContentVerifier get(AlgorithmIdentifier algorithm) + throws OperatorCreationException + { + try + { + Signature sig = helper.createSignature(algorithm); + + sig.initVerify(certificate.getPublicKey()); + + stream = new SignatureOutputStream(sig); + } + catch (GeneralSecurityException e) + { + throw new OperatorCreationException("exception on setup: " + e, e); + } + + Signature rawSig = createRawSig(algorithm, certificate.getPublicKey()); + + if (rawSig != null) + { + return new RawSigVerifier(algorithm, stream, rawSig); + } + else + { + return new SigVerifier(algorithm, stream); + } + } + }; + } + + public ContentVerifierProvider build(final PublicKey publicKey) + throws OperatorCreationException + { + return new ContentVerifierProvider() + { + public boolean hasAssociatedCertificate() + { + return false; + } + + public X509CertificateHolder getAssociatedCertificate() + { + return null; + } + + public ContentVerifier get(AlgorithmIdentifier algorithm) + throws OperatorCreationException + { + SignatureOutputStream stream = createSignatureStream(algorithm, publicKey); + + Signature rawSig = createRawSig(algorithm, publicKey); + + if (rawSig != null) + { + return new RawSigVerifier(algorithm, stream, rawSig); + } + else + { + return new SigVerifier(algorithm, stream); + } + } + }; + } + + public ContentVerifierProvider build(SubjectPublicKeyInfo publicKey) + throws OperatorCreationException + { + return this.build(helper.convertPublicKey(publicKey)); + } + + private SignatureOutputStream createSignatureStream(AlgorithmIdentifier algorithm, PublicKey publicKey) + throws OperatorCreationException + { + try + { + Signature sig = helper.createSignature(algorithm); + + sig.initVerify(publicKey); + + return new SignatureOutputStream(sig); + } + catch (GeneralSecurityException e) + { + throw new OperatorCreationException("exception on setup: " + e, e); + } + } + + private Signature createRawSig(AlgorithmIdentifier algorithm, PublicKey publicKey) + { + Signature rawSig; + try + { + rawSig = helper.createRawSignature(algorithm); + + if (rawSig != null) + { + rawSig.initVerify(publicKey); + } + } + catch (Exception e) + { + rawSig = null; + } + return rawSig; + } + + private class SigVerifier + implements ContentVerifier + { + private SignatureOutputStream stream; + private AlgorithmIdentifier algorithm; + + SigVerifier(AlgorithmIdentifier algorithm, SignatureOutputStream stream) + { + this.algorithm = algorithm; + this.stream = stream; + } + + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return algorithm; + } + + public OutputStream getOutputStream() + { + if (stream == null) + { + throw new IllegalStateException("verifier not initialised"); + } + + return stream; + } + + public boolean verify(byte[] expected) + { + try + { + return stream.verify(expected); + } + catch (SignatureException e) + { + throw new RuntimeOperatorException("exception obtaining signature: " + e.getMessage(), e); + } + } + } + + private class RawSigVerifier + extends SigVerifier + implements RawContentVerifier + { + private Signature rawSignature; + + RawSigVerifier(AlgorithmIdentifier algorithm, SignatureOutputStream stream, Signature rawSignature) + { + super(algorithm, stream); + this.rawSignature = rawSignature; + } + + public boolean verify(byte[] digest, byte[] expected) + { + try + { + rawSignature.update(digest); + + return rawSignature.verify(expected); + } + catch (SignatureException e) + { + throw new RuntimeOperatorException("exception obtaining raw signature: " + e.getMessage(), e); + } + } + } + + private class SignatureOutputStream + extends OutputStream + { + private Signature sig; + + SignatureOutputStream(Signature sig) + { + this.sig = sig; + } + + public void write(byte[] bytes, int off, int len) + throws IOException + { + try + { + sig.update(bytes, off, len); + } + catch (SignatureException e) + { + throw new OperatorStreamException("exception in content signer: " + e.getMessage(), e); + } + } + + public void write(byte[] bytes) + throws IOException + { + try + { + sig.update(bytes); + } + catch (SignatureException e) + { + throw new OperatorStreamException("exception in content signer: " + e.getMessage(), e); + } + } + + public void write(int b) + throws IOException + { + try + { + sig.update((byte)b); + } + catch (SignatureException e) + { + throw new OperatorStreamException("exception in content signer: " + e.getMessage(), e); + } + } + + boolean verify(byte[] expected) + throws SignatureException + { + return sig.verify(expected); + } + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/JcaDigestCalculatorProviderBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/JcaDigestCalculatorProviderBuilder.java new file mode 100644 index 000000000..849126232 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/JcaDigestCalculatorProviderBuilder.java @@ -0,0 +1,114 @@ +package org.spongycastle.operator.jcajce; + +import java.io.IOException; +import java.io.OutputStream; +import java.security.GeneralSecurityException; +import java.security.MessageDigest; +import java.security.Provider; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.jcajce.DefaultJcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.ProviderJcaJceHelper; +import org.spongycastle.operator.DigestCalculator; +import org.spongycastle.operator.DigestCalculatorProvider; +import org.spongycastle.operator.OperatorCreationException; + +public class JcaDigestCalculatorProviderBuilder +{ + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + + public JcaDigestCalculatorProviderBuilder() + { + } + + public JcaDigestCalculatorProviderBuilder setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + + return this; + } + + public JcaDigestCalculatorProviderBuilder setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + + return this; + } + + public DigestCalculatorProvider build() + throws OperatorCreationException + { + return new DigestCalculatorProvider() + { + public DigestCalculator get(final AlgorithmIdentifier algorithm) + throws OperatorCreationException + { + final DigestOutputStream stream; + + try + { + MessageDigest dig = helper.createDigest(algorithm); + + stream = new DigestOutputStream(dig); + } + catch (GeneralSecurityException e) + { + throw new OperatorCreationException("exception on setup: " + e, e); + } + + return new DigestCalculator() + { + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return algorithm; + } + + public OutputStream getOutputStream() + { + return stream; + } + + public byte[] getDigest() + { + return stream.getDigest(); + } + }; + } + }; + } + + private class DigestOutputStream + extends OutputStream + { + private MessageDigest dig; + + DigestOutputStream(MessageDigest dig) + { + this.dig = dig; + } + + public void write(byte[] bytes, int off, int len) + throws IOException + { + dig.update(bytes, off, len); + } + + public void write(byte[] bytes) + throws IOException + { + dig.update(bytes); + } + + public void write(int b) + throws IOException + { + dig.update((byte)b); + } + + byte[] getDigest() + { + return dig.digest(); + } + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/JceAsymmetricKeyUnwrapper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/JceAsymmetricKeyUnwrapper.java new file mode 100644 index 000000000..fd70b040e --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/JceAsymmetricKeyUnwrapper.java @@ -0,0 +1,133 @@ +package org.spongycastle.operator.jcajce; + +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.ProviderException; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.spec.SecretKeySpec; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.jcajce.DefaultJcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.ProviderJcaJceHelper; +import org.spongycastle.operator.AsymmetricKeyUnwrapper; +import org.spongycastle.operator.GenericKey; +import org.spongycastle.operator.OperatorException; + +public class JceAsymmetricKeyUnwrapper + extends AsymmetricKeyUnwrapper +{ + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + private Map extraMappings = new HashMap(); + private PrivateKey privKey; + + public JceAsymmetricKeyUnwrapper(AlgorithmIdentifier algorithmIdentifier, PrivateKey privKey) + { + super(algorithmIdentifier); + + this.privKey = privKey; + } + + public JceAsymmetricKeyUnwrapper setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + + return this; + } + + public JceAsymmetricKeyUnwrapper setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + + return this; + } + + /** + * Internally algorithm ids are converted into cipher names using a lookup table. For some providers + * the standard lookup table won't work. Use this method to establish a specific mapping from an + * algorithm identifier to a specific algorithm. + * <p> + * For example: + * <pre> + * unwrapper.setAlgorithmMapping(PKCSObjectIdentifiers.rsaEncryption, "RSA"); + * </pre> + * </p> + * @param algorithm OID of algorithm in recipient. + * @param algorithmName JCE algorithm name to use. + * @return the current Unwrapper. + */ + public JceAsymmetricKeyUnwrapper setAlgorithmMapping(ASN1ObjectIdentifier algorithm, String algorithmName) + { + extraMappings.put(algorithm, algorithmName); + + return this; + } + + public GenericKey generateUnwrappedKey(AlgorithmIdentifier encryptedKeyAlgorithm, byte[] encryptedKey) + throws OperatorException + { + try + { + Key sKey = null; + + Cipher keyCipher = helper.createAsymmetricWrapper(this.getAlgorithmIdentifier().getAlgorithm(), extraMappings); + AlgorithmParameters algParams = helper.createAlgorithmParameters(this.getAlgorithmIdentifier()); + + try + { + if (algParams != null) + { + keyCipher.init(Cipher.UNWRAP_MODE, privKey, algParams); + } + else + { + keyCipher.init(Cipher.UNWRAP_MODE, privKey); + } + sKey = keyCipher.unwrap(encryptedKey, helper.getKeyAlgorithmName(encryptedKeyAlgorithm.getAlgorithm()), Cipher.SECRET_KEY); + } + catch (GeneralSecurityException e) + { + } + catch (IllegalStateException e) + { + } + catch (UnsupportedOperationException e) + { + } + catch (ProviderException e) + { + } + + // some providers do not support UNWRAP (this appears to be only for asymmetric algorithms) + if (sKey == null) + { + keyCipher.init(Cipher.DECRYPT_MODE, privKey); + sKey = new SecretKeySpec(keyCipher.doFinal(encryptedKey), encryptedKeyAlgorithm.getAlgorithm().getId()); + } + + return new JceGenericKey(encryptedKeyAlgorithm, sKey); + } + catch (InvalidKeyException e) + { + throw new OperatorException("key invalid: " + e.getMessage(), e); + } + catch (IllegalBlockSizeException e) + { + throw new OperatorException("illegal blocksize: " + e.getMessage(), e); + } + catch (BadPaddingException e) + { + throw new OperatorException("bad padding: " + e.getMessage(), e); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/JceAsymmetricKeyWrapper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/JceAsymmetricKeyWrapper.java new file mode 100644 index 000000000..66d31b226 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/JceAsymmetricKeyWrapper.java @@ -0,0 +1,157 @@ +package org.spongycastle.operator.jcajce; + +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.Provider; +import java.security.ProviderException; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.Cipher; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.jcajce.DefaultJcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.ProviderJcaJceHelper; +import org.spongycastle.operator.AsymmetricKeyWrapper; +import org.spongycastle.operator.GenericKey; +import org.spongycastle.operator.OperatorException; + +public class JceAsymmetricKeyWrapper + extends AsymmetricKeyWrapper +{ + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + private Map extraMappings = new HashMap(); + private PublicKey publicKey; + private SecureRandom random; + + public JceAsymmetricKeyWrapper(PublicKey publicKey) + { + super(SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()).getAlgorithm()); + + this.publicKey = publicKey; + } + + public JceAsymmetricKeyWrapper(X509Certificate certificate) + { + this(certificate.getPublicKey()); + } + + /** + * Create a wrapper, overriding the algorithm type that is stored in the public key. + * + * @param algorithmIdentifier identifier for encryption algorithm to be used. + * @param publicKey the public key to be used. + */ + public JceAsymmetricKeyWrapper(AlgorithmIdentifier algorithmIdentifier, PublicKey publicKey) + { + super(algorithmIdentifier); + + this.publicKey = publicKey; + } + + public JceAsymmetricKeyWrapper setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + + return this; + } + + public JceAsymmetricKeyWrapper setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + + return this; + } + + public JceAsymmetricKeyWrapper setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + /** + * Internally algorithm ids are converted into cipher names using a lookup table. For some providers + * the standard lookup table won't work. Use this method to establish a specific mapping from an + * algorithm identifier to a specific algorithm. + * <p> + * For example: + * <pre> + * unwrapper.setAlgorithmMapping(PKCSObjectIdentifiers.rsaEncryption, "RSA"); + * </pre> + * </p> + * @param algorithm OID of algorithm in recipient. + * @param algorithmName JCE algorithm name to use. + * @return the current Wrapper. + */ + public JceAsymmetricKeyWrapper setAlgorithmMapping(ASN1ObjectIdentifier algorithm, String algorithmName) + { + extraMappings.put(algorithm, algorithmName); + + return this; + } + + public byte[] generateWrappedKey(GenericKey encryptionKey) + throws OperatorException + { + Cipher keyEncryptionCipher = helper.createAsymmetricWrapper(getAlgorithmIdentifier().getAlgorithm(), extraMappings); + AlgorithmParameters algParams = helper.createAlgorithmParameters(this.getAlgorithmIdentifier()); + + byte[] encryptedKeyBytes = null; + + try + { + if (algParams != null) + { + keyEncryptionCipher.init(Cipher.WRAP_MODE, publicKey, algParams, random); + } + else + { + keyEncryptionCipher.init(Cipher.WRAP_MODE, publicKey, random); + } + encryptedKeyBytes = keyEncryptionCipher.wrap(OperatorUtils.getJceKey(encryptionKey)); + } + catch (InvalidKeyException e) + { + } + catch (GeneralSecurityException e) + { + } + catch (IllegalStateException e) + { + } + catch (UnsupportedOperationException e) + { + } + catch (ProviderException e) + { + } + + // some providers do not support WRAP (this appears to be only for asymmetric algorithms) + if (encryptedKeyBytes == null) + { + try + { + keyEncryptionCipher.init(Cipher.ENCRYPT_MODE, publicKey, random); + encryptedKeyBytes = keyEncryptionCipher.doFinal(OperatorUtils.getJceKey(encryptionKey).getEncoded()); + } + catch (InvalidKeyException e) + { + throw new OperatorException("unable to encrypt contents key", e); + } + catch (GeneralSecurityException e) + { + throw new OperatorException("unable to encrypt contents key", e); + } + } + + return encryptedKeyBytes; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/JceGenericKey.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/JceGenericKey.java new file mode 100644 index 000000000..a11a535ec --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/JceGenericKey.java @@ -0,0 +1,33 @@ +package org.spongycastle.operator.jcajce; + +import java.security.Key; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.operator.GenericKey; + +public class JceGenericKey + extends GenericKey +{ + /** + * Attempt to simplify the key representation if possible. + * + * @param key a provider based key + * @return the byte encoding if one exists, key object otherwise. + */ + private static Object getRepresentation(Key key) + { + byte[] keyBytes = key.getEncoded(); + + if (keyBytes != null) + { + return keyBytes; + } + + return key; + } + + public JceGenericKey(AlgorithmIdentifier algorithmIdentifier, Key representation) + { + super(algorithmIdentifier, getRepresentation(representation)); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/JceSymmetricKeyUnwrapper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/JceSymmetricKeyUnwrapper.java new file mode 100644 index 000000000..74ab54167 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/JceSymmetricKeyUnwrapper.java @@ -0,0 +1,65 @@ +package org.spongycastle.operator.jcajce; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; + +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.jcajce.DefaultJcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.ProviderJcaJceHelper; +import org.spongycastle.operator.GenericKey; +import org.spongycastle.operator.OperatorException; +import org.spongycastle.operator.SymmetricKeyUnwrapper; + +public class JceSymmetricKeyUnwrapper + extends SymmetricKeyUnwrapper +{ + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + private SecretKey secretKey; + + public JceSymmetricKeyUnwrapper(AlgorithmIdentifier algorithmIdentifier, SecretKey secretKey) + { + super(algorithmIdentifier); + + this.secretKey = secretKey; + } + + public JceSymmetricKeyUnwrapper setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + + return this; + } + + public JceSymmetricKeyUnwrapper setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + + return this; + } + + public GenericKey generateUnwrappedKey(AlgorithmIdentifier encryptedKeyAlgorithm, byte[] encryptedKey) + throws OperatorException + { + try + { + Cipher keyCipher = helper.createSymmetricWrapper(this.getAlgorithmIdentifier().getAlgorithm()); + + keyCipher.init(Cipher.UNWRAP_MODE, secretKey); + + return new JceGenericKey(encryptedKeyAlgorithm, keyCipher.unwrap(encryptedKey, helper.getKeyAlgorithmName(encryptedKeyAlgorithm.getAlgorithm()), Cipher.SECRET_KEY)); + } + catch (InvalidKeyException e) + { + throw new OperatorException("key invalid in message.", e); + } + catch (NoSuchAlgorithmException e) + { + throw new OperatorException("can't find algorithm.", e); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/JceSymmetricKeyWrapper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/JceSymmetricKeyWrapper.java new file mode 100644 index 000000000..55b2287a6 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/JceSymmetricKeyWrapper.java @@ -0,0 +1,154 @@ +package org.spongycastle.operator.jcajce; + +import java.security.GeneralSecurityException; +import java.security.Key; +import java.security.Provider; +import java.security.SecureRandom; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; + +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.DERNull; +import org.spongycastle.asn1.kisa.KISAObjectIdentifiers; +import org.spongycastle.asn1.nist.NISTObjectIdentifiers; +import org.spongycastle.asn1.ntt.NTTObjectIdentifiers; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.jcajce.DefaultJcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.ProviderJcaJceHelper; +import org.spongycastle.operator.GenericKey; +import org.spongycastle.operator.OperatorException; +import org.spongycastle.operator.SymmetricKeyWrapper; + +public class JceSymmetricKeyWrapper + extends SymmetricKeyWrapper +{ + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + private SecureRandom random; + private SecretKey wrappingKey; + + public JceSymmetricKeyWrapper(SecretKey wrappingKey) + { + super(determineKeyEncAlg(wrappingKey)); + + this.wrappingKey = wrappingKey; + } + + public JceSymmetricKeyWrapper setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + + return this; + } + + public JceSymmetricKeyWrapper setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + + return this; + } + + public JceSymmetricKeyWrapper setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + public byte[] generateWrappedKey(GenericKey encryptionKey) + throws OperatorException + { + Key contentEncryptionKeySpec = OperatorUtils.getJceKey(encryptionKey); + + Cipher keyEncryptionCipher = helper.createSymmetricWrapper(this.getAlgorithmIdentifier().getAlgorithm()); + + try + { + keyEncryptionCipher.init(Cipher.WRAP_MODE, wrappingKey, random); + + return keyEncryptionCipher.wrap(contentEncryptionKeySpec); + } + catch (GeneralSecurityException e) + { + throw new OperatorException("cannot wrap key: " + e.getMessage(), e); + } + } + + private static AlgorithmIdentifier determineKeyEncAlg(SecretKey key) + { + String algorithm = key.getAlgorithm(); + + if (algorithm.startsWith("DES")) + { + return new AlgorithmIdentifier(new ASN1ObjectIdentifier( + "1.2.840.113549.1.9.16.3.6"), DERNull.INSTANCE); + } + else if (algorithm.startsWith("RC2")) + { + return new AlgorithmIdentifier(new ASN1ObjectIdentifier( + "1.2.840.113549.1.9.16.3.7"), new ASN1Integer(58)); + } + else if (algorithm.startsWith("AES")) + { + int length = key.getEncoded().length * 8; + ASN1ObjectIdentifier wrapOid; + + if (length == 128) + { + wrapOid = NISTObjectIdentifiers.id_aes128_wrap; + } + else if (length == 192) + { + wrapOid = NISTObjectIdentifiers.id_aes192_wrap; + } + else if (length == 256) + { + wrapOid = NISTObjectIdentifiers.id_aes256_wrap; + } + else + { + throw new IllegalArgumentException("illegal keysize in AES"); + } + + return new AlgorithmIdentifier(wrapOid); // parameters absent + } + else if (algorithm.startsWith("SEED")) + { + // parameters absent + return new AlgorithmIdentifier( + KISAObjectIdentifiers.id_npki_app_cmsSeed_wrap); + } + else if (algorithm.startsWith("Camellia")) + { + int length = key.getEncoded().length * 8; + ASN1ObjectIdentifier wrapOid; + + if (length == 128) + { + wrapOid = NTTObjectIdentifiers.id_camellia128_wrap; + } + else if (length == 192) + { + wrapOid = NTTObjectIdentifiers.id_camellia192_wrap; + } + else if (length == 256) + { + wrapOid = NTTObjectIdentifiers.id_camellia256_wrap; + } + else + { + throw new IllegalArgumentException( + "illegal keysize in Camellia"); + } + + return new AlgorithmIdentifier(wrapOid); // parameters must be + // absent + } + else + { + throw new IllegalArgumentException("unknown algorithm"); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/OperatorHelper.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/OperatorHelper.java new file mode 100644 index 000000000..2a4ded274 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/OperatorHelper.java @@ -0,0 +1,469 @@ +package org.spongycastle.operator.jcajce; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PSSParameterSpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.Cipher; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.DERNull; +import org.spongycastle.asn1.cryptopro.CryptoProObjectIdentifiers; +import org.spongycastle.asn1.kisa.KISAObjectIdentifiers; +import org.spongycastle.asn1.nist.NISTObjectIdentifiers; +import org.spongycastle.asn1.ntt.NTTObjectIdentifiers; +import org.spongycastle.asn1.oiw.OIWObjectIdentifiers; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.pkcs.RSASSAPSSparams; +import org.spongycastle.asn1.teletrust.TeleTrusTObjectIdentifiers; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.asn1.x9.X9ObjectIdentifiers; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.jcajce.JcaJceHelper; +import org.spongycastle.jcajce.JcaJceUtils; +import org.spongycastle.operator.OperatorCreationException; + +class OperatorHelper +{ + private static final Map oids = new HashMap(); + private static final Map asymmetricWrapperAlgNames = new HashMap(); + private static final Map symmetricWrapperAlgNames = new HashMap(); + private static final Map symmetricKeyAlgNames = new HashMap(); + + static + { + // + // reverse mappings + // + oids.put(new ASN1ObjectIdentifier("1.2.840.113549.1.1.5"), "SHA1WITHRSA"); + oids.put(PKCSObjectIdentifiers.sha224WithRSAEncryption, "SHA224WITHRSA"); + oids.put(PKCSObjectIdentifiers.sha256WithRSAEncryption, "SHA256WITHRSA"); + oids.put(PKCSObjectIdentifiers.sha384WithRSAEncryption, "SHA384WITHRSA"); + oids.put(PKCSObjectIdentifiers.sha512WithRSAEncryption, "SHA512WITHRSA"); + oids.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_94, "GOST3411WITHGOST3410"); + oids.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_2001, "GOST3411WITHECGOST3410"); + + oids.put(new ASN1ObjectIdentifier("1.2.840.113549.1.1.4"), "MD5WITHRSA"); + oids.put(new ASN1ObjectIdentifier("1.2.840.113549.1.1.2"), "MD2WITHRSA"); + oids.put(new ASN1ObjectIdentifier("1.2.840.10040.4.3"), "SHA1WITHDSA"); + oids.put(X9ObjectIdentifiers.ecdsa_with_SHA1, "SHA1WITHECDSA"); + oids.put(X9ObjectIdentifiers.ecdsa_with_SHA224, "SHA224WITHECDSA"); + oids.put(X9ObjectIdentifiers.ecdsa_with_SHA256, "SHA256WITHECDSA"); + oids.put(X9ObjectIdentifiers.ecdsa_with_SHA384, "SHA384WITHECDSA"); + oids.put(X9ObjectIdentifiers.ecdsa_with_SHA512, "SHA512WITHECDSA"); + oids.put(OIWObjectIdentifiers.sha1WithRSA, "SHA1WITHRSA"); + oids.put(OIWObjectIdentifiers.dsaWithSHA1, "SHA1WITHDSA"); + oids.put(NISTObjectIdentifiers.dsa_with_sha224, "SHA224WITHDSA"); + oids.put(NISTObjectIdentifiers.dsa_with_sha256, "SHA256WITHDSA"); + + oids.put(OIWObjectIdentifiers.idSHA1, "SHA-1"); + oids.put(NISTObjectIdentifiers.id_sha224, "SHA-224"); + oids.put(NISTObjectIdentifiers.id_sha256, "SHA-256"); + oids.put(NISTObjectIdentifiers.id_sha384, "SHA-384"); + oids.put(NISTObjectIdentifiers.id_sha512, "SHA-512"); + oids.put(TeleTrusTObjectIdentifiers.ripemd128, "RIPEMD-128"); + oids.put(TeleTrusTObjectIdentifiers.ripemd160, "RIPEMD-160"); + oids.put(TeleTrusTObjectIdentifiers.ripemd256, "RIPEMD-256"); + + asymmetricWrapperAlgNames.put(PKCSObjectIdentifiers.rsaEncryption, "RSA/ECB/PKCS1Padding"); + + symmetricWrapperAlgNames.put(PKCSObjectIdentifiers.id_alg_CMS3DESwrap, "DESEDEWrap"); + symmetricWrapperAlgNames.put(PKCSObjectIdentifiers.id_alg_CMSRC2wrap, "RC2Wrap"); + symmetricWrapperAlgNames.put(NISTObjectIdentifiers.id_aes128_wrap, "AESWrap"); + symmetricWrapperAlgNames.put(NISTObjectIdentifiers.id_aes192_wrap, "AESWrap"); + symmetricWrapperAlgNames.put(NISTObjectIdentifiers.id_aes256_wrap, "AESWrap"); + symmetricWrapperAlgNames.put(NTTObjectIdentifiers.id_camellia128_wrap, "CamelliaWrap"); + symmetricWrapperAlgNames.put(NTTObjectIdentifiers.id_camellia192_wrap, "CamelliaWrap"); + symmetricWrapperAlgNames.put(NTTObjectIdentifiers.id_camellia256_wrap, "CamelliaWrap"); + symmetricWrapperAlgNames.put(KISAObjectIdentifiers.id_npki_app_cmsSeed_wrap, "SEEDWrap"); + symmetricWrapperAlgNames.put(PKCSObjectIdentifiers.des_EDE3_CBC, "DESede"); + + symmetricKeyAlgNames.put(NISTObjectIdentifiers.aes, "AES"); + symmetricKeyAlgNames.put(NISTObjectIdentifiers.id_aes128_CBC, "AES"); + symmetricKeyAlgNames.put(NISTObjectIdentifiers.id_aes192_CBC, "AES"); + symmetricKeyAlgNames.put(NISTObjectIdentifiers.id_aes256_CBC, "AES"); + symmetricKeyAlgNames.put(PKCSObjectIdentifiers.des_EDE3_CBC, "DESede"); + symmetricKeyAlgNames.put(PKCSObjectIdentifiers.RC2_CBC, "RC2"); + } + + private JcaJceHelper helper; + + OperatorHelper(JcaJceHelper helper) + { + this.helper = helper; + } + + Cipher createAsymmetricWrapper(ASN1ObjectIdentifier algorithm, Map extraAlgNames) + throws OperatorCreationException + { + try + { + String cipherName = null; + + if (!extraAlgNames.isEmpty()) + { + cipherName = (String)extraAlgNames.get(algorithm); + } + + if (cipherName == null) + { + cipherName = (String)asymmetricWrapperAlgNames.get(algorithm); + } + + if (cipherName != null) + { + try + { + // this is reversed as the Sun policy files now allow unlimited strength RSA + return helper.createCipher(cipherName); + } + catch (NoSuchAlgorithmException e) + { + // try alternate for RSA + if (cipherName.equals("RSA/ECB/PKCS1Padding")) + { + try + { + return helper.createCipher("RSA/NONE/PKCS1Padding"); + } + catch (NoSuchAlgorithmException ex) + { + // Ignore + } + } + // Ignore + } + } + + return helper.createCipher(algorithm.getId()); + } + catch (GeneralSecurityException e) + { + throw new OperatorCreationException("cannot create cipher: " + e.getMessage(), e); + } + } + + Cipher createSymmetricWrapper(ASN1ObjectIdentifier algorithm) + throws OperatorCreationException + { + try + { + String cipherName = (String)symmetricWrapperAlgNames.get(algorithm); + + if (cipherName != null) + { + try + { + // this is reversed as the Sun policy files now allow unlimited strength RSA + return helper.createCipher(cipherName); + } + catch (NoSuchAlgorithmException e) + { + // Ignore + } + } + return helper.createCipher(algorithm.getId()); + } + catch (GeneralSecurityException e) + { + throw new OperatorCreationException("cannot create cipher: " + e.getMessage(), e); + } + } + + AlgorithmParameters createAlgorithmParameters(AlgorithmIdentifier cipherAlgId) + throws OperatorCreationException + { + AlgorithmParameters parameters; + + if (cipherAlgId.getAlgorithm().equals(PKCSObjectIdentifiers.rsaEncryption)) + { + return null; + } + + try + { + parameters = helper.createAlgorithmParameters(cipherAlgId.getAlgorithm().getId()); + } + catch (NoSuchAlgorithmException e) + { + return null; // There's a good chance there aren't any! + } + catch (NoSuchProviderException e) + { + throw new OperatorCreationException("cannot create algorithm parameters: " + e.getMessage(), e); + } + + try + { + parameters.init(cipherAlgId.getParameters().toASN1Primitive().getEncoded()); + } + catch (IOException e) + { + throw new OperatorCreationException("cannot initialise algorithm parameters: " + e.getMessage(), e); + } + + return parameters; + } + + MessageDigest createDigest(AlgorithmIdentifier digAlgId) + throws GeneralSecurityException + { + MessageDigest dig; + + try + { + dig = helper.createDigest(getDigestAlgName(digAlgId.getAlgorithm())); + } + catch (NoSuchAlgorithmException e) + { + // + // try an alternate + // + if (oids.get(digAlgId.getAlgorithm()) != null) + { + String digestAlgorithm = (String)oids.get(digAlgId.getAlgorithm()); + + dig = helper.createDigest(digestAlgorithm); + } + else + { + throw e; + } + } + + return dig; + } + + Signature createSignature(AlgorithmIdentifier sigAlgId) + throws GeneralSecurityException + { + Signature sig; + + try + { + sig = helper.createSignature(getSignatureName(sigAlgId)); + } + catch (NoSuchAlgorithmException e) + { + // + // try an alternate + // + if (oids.get(sigAlgId.getAlgorithm()) != null) + { + String signatureAlgorithm = (String)oids.get(sigAlgId.getAlgorithm()); + + sig = helper.createSignature(signatureAlgorithm); + } + else + { + throw e; + } + } + + return sig; + } + + public Signature createRawSignature(AlgorithmIdentifier algorithm) + { + Signature sig; + + try + { + String algName = getSignatureName(algorithm); + + algName = "NONE" + algName.substring(algName.indexOf("WITH")); + + sig = helper.createSignature(algName); + + // RFC 4056 + // When the id-RSASSA-PSS algorithm identifier is used for a signature, + // the AlgorithmIdentifier parameters field MUST contain RSASSA-PSS-params. + if (algorithm.getAlgorithm().equals(PKCSObjectIdentifiers.id_RSASSA_PSS)) + { + AlgorithmParameters params = helper.createAlgorithmParameters(algName); + + JcaJceUtils.loadParameters(params, algorithm.getParameters()); + + PSSParameterSpec spec = (PSSParameterSpec)params.getParameterSpec(PSSParameterSpec.class); + sig.setParameter(spec); + } + } + catch (Exception e) + { + return null; + } + + return sig; + } + + private static String getSignatureName( + AlgorithmIdentifier sigAlgId) + { + ASN1Encodable params = sigAlgId.getParameters(); + + if (params != null && !DERNull.INSTANCE.equals(params)) + { + if (sigAlgId.getAlgorithm().equals(PKCSObjectIdentifiers.id_RSASSA_PSS)) + { + RSASSAPSSparams rsaParams = RSASSAPSSparams.getInstance(params); + return getDigestAlgName(rsaParams.getHashAlgorithm().getAlgorithm()) + "WITHRSAANDMGF1"; + } + } + + if (oids.containsKey(sigAlgId.getAlgorithm())) + { + return (String)oids.get(sigAlgId.getAlgorithm()); + } + + return sigAlgId.getAlgorithm().getId(); + } + + private static String getDigestAlgName( + ASN1ObjectIdentifier digestAlgOID) + { + if (PKCSObjectIdentifiers.md5.equals(digestAlgOID)) + { + return "MD5"; + } + else if (OIWObjectIdentifiers.idSHA1.equals(digestAlgOID)) + { + return "SHA1"; + } + else if (NISTObjectIdentifiers.id_sha224.equals(digestAlgOID)) + { + return "SHA224"; + } + else if (NISTObjectIdentifiers.id_sha256.equals(digestAlgOID)) + { + return "SHA256"; + } + else if (NISTObjectIdentifiers.id_sha384.equals(digestAlgOID)) + { + return "SHA384"; + } + else if (NISTObjectIdentifiers.id_sha512.equals(digestAlgOID)) + { + return "SHA512"; + } + else if (TeleTrusTObjectIdentifiers.ripemd128.equals(digestAlgOID)) + { + return "RIPEMD128"; + } + else if (TeleTrusTObjectIdentifiers.ripemd160.equals(digestAlgOID)) + { + return "RIPEMD160"; + } + else if (TeleTrusTObjectIdentifiers.ripemd256.equals(digestAlgOID)) + { + return "RIPEMD256"; + } + else if (CryptoProObjectIdentifiers.gostR3411.equals(digestAlgOID)) + { + return "GOST3411"; + } + else + { + return digestAlgOID.getId(); + } + } + + public X509Certificate convertCertificate(X509CertificateHolder certHolder) + throws CertificateException + { + + try + { + CertificateFactory certFact = helper.createCertificateFactory("X.509"); + + return (X509Certificate)certFact.generateCertificate(new ByteArrayInputStream(certHolder.getEncoded())); + } + catch (IOException e) + { + throw new OpCertificateException("cannot get encoded form of certificate: " + e.getMessage(), e); + } + catch (NoSuchAlgorithmException e) + { + throw new OpCertificateException("cannot create certificate factory: " + e.getMessage(), e); + } + catch (NoSuchProviderException e) + { + throw new OpCertificateException("cannot find factory provider: " + e.getMessage(), e); + } + } + + public PublicKey convertPublicKey(SubjectPublicKeyInfo publicKeyInfo) + throws OperatorCreationException + { + try + { + KeyFactory keyFact = helper.createKeyFactory(publicKeyInfo.getAlgorithm().getAlgorithm().getId()); + + return keyFact.generatePublic(new X509EncodedKeySpec(publicKeyInfo.getEncoded())); + } + catch (IOException e) + { + throw new OperatorCreationException("cannot get encoded form of key: " + e.getMessage(), e); + } + catch (NoSuchAlgorithmException e) + { + throw new OperatorCreationException("cannot create key factory: " + e.getMessage(), e); + } + catch (NoSuchProviderException e) + { + throw new OperatorCreationException("cannot find factory provider: " + e.getMessage(), e); + } + catch (InvalidKeySpecException e) + { + throw new OperatorCreationException("cannot create key factory: " + e.getMessage(), e); + } + } + + // TODO: put somewhere public so cause easily accessed + private static class OpCertificateException + extends CertificateException + { + private Throwable cause; + + public OpCertificateException(String msg, Throwable cause) + { + super(msg); + + this.cause = cause; + } + + public Throwable getCause() + { + return cause; + } + } + + String getKeyAlgorithmName(ASN1ObjectIdentifier oid) + { + + String name = (String)symmetricKeyAlgNames.get(oid); + + if (name != null) + { + return name; + } + + return oid.getId(); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/OperatorUtils.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/OperatorUtils.java new file mode 100644 index 000000000..81cb40634 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/operator/jcajce/OperatorUtils.java @@ -0,0 +1,25 @@ +package org.spongycastle.operator.jcajce; + +import java.security.Key; + +import javax.crypto.spec.SecretKeySpec; + +import org.spongycastle.operator.GenericKey; + +class OperatorUtils +{ + static Key getJceKey(GenericKey key) + { + if (key.getRepresentation() instanceof Key) + { + return (Key)key.getRepresentation(); + } + + if (key.getRepresentation() instanceof byte[]) + { + return new SecretKeySpec((byte[])key.getRepresentation(), "ENC"); + } + + throw new IllegalArgumentException("unknown generic key type"); + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/MacDataGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/MacDataGenerator.java new file mode 100644 index 000000000..c6667d8b1 --- /dev/null +++ b/libraries/spongycastle/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/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/PKCS10CertificationRequest.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/PKCS10CertificationRequest.java new file mode 100644 index 000000000..c4ee749ac --- /dev/null +++ b/libraries/spongycastle/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/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/PKCS10CertificationRequestBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/PKCS10CertificationRequestBuilder.java new file mode 100644 index 000000000..d10c6fddd --- /dev/null +++ b/libraries/spongycastle/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/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/PKCS12MacCalculatorBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/PKCS12MacCalculatorBuilder.java new file mode 100644 index 000000000..be82e9908 --- /dev/null +++ b/libraries/spongycastle/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/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/PKCS12MacCalculatorBuilderProvider.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/PKCS12MacCalculatorBuilderProvider.java new file mode 100644 index 000000000..52c9d1cd3 --- /dev/null +++ b/libraries/spongycastle/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/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/PKCS12PfxPdu.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/PKCS12PfxPdu.java new file mode 100644 index 000000000..6a229e486 --- /dev/null +++ b/libraries/spongycastle/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/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/PKCS12PfxPduBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/PKCS12PfxPduBuilder.java new file mode 100644 index 000000000..a9cb0b5be --- /dev/null +++ b/libraries/spongycastle/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/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/PKCS12SafeBag.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/PKCS12SafeBag.java new file mode 100644 index 000000000..0ce0887e7 --- /dev/null +++ b/libraries/spongycastle/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/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/PKCS12SafeBagBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/PKCS12SafeBagBuilder.java new file mode 100644 index 000000000..d60bd5a5c --- /dev/null +++ b/libraries/spongycastle/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/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/PKCS12SafeBagFactory.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/PKCS12SafeBagFactory.java new file mode 100644 index 000000000..36f53723a --- /dev/null +++ b/libraries/spongycastle/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/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/PKCS8EncryptedPrivateKeyInfo.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/PKCS8EncryptedPrivateKeyInfo.java new file mode 100644 index 000000000..1f41fb677 --- /dev/null +++ b/libraries/spongycastle/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/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/PKCS8EncryptedPrivateKeyInfoBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/PKCS8EncryptedPrivateKeyInfoBuilder.java new file mode 100644 index 000000000..3897c40ae --- /dev/null +++ b/libraries/spongycastle/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/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/PKCSException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/PKCSException.java new file mode 100644 index 000000000..201dde938 --- /dev/null +++ b/libraries/spongycastle/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/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/PKCSIOException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/PKCSIOException.java new file mode 100644 index 000000000..0352829b9 --- /dev/null +++ b/libraries/spongycastle/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/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/bc/BcPKCS10CertificationRequest.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/bc/BcPKCS10CertificationRequest.java new file mode 100644 index 000000000..6ac22464c --- /dev/null +++ b/libraries/spongycastle/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/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/bc/BcPKCS10CertificationRequestBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/bc/BcPKCS10CertificationRequestBuilder.java new file mode 100644 index 000000000..1590a6634 --- /dev/null +++ b/libraries/spongycastle/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/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/bc/BcPKCS12MacCalculatorBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/bc/BcPKCS12MacCalculatorBuilder.java new file mode 100644 index 000000000..456d5b080 --- /dev/null +++ b/libraries/spongycastle/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/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/bc/BcPKCS12MacCalculatorBuilderProvider.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/bc/BcPKCS12MacCalculatorBuilderProvider.java new file mode 100644 index 000000000..d5533753a --- /dev/null +++ b/libraries/spongycastle/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/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/bc/BcPKCS12PBEInputDecryptorProviderBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/bc/BcPKCS12PBEInputDecryptorProviderBuilder.java new file mode 100644 index 000000000..a4618c042 --- /dev/null +++ b/libraries/spongycastle/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/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/bc/BcPKCS12PBEOutputEncryptorBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/bc/BcPKCS12PBEOutputEncryptorBuilder.java new file mode 100644 index 000000000..d8af97c39 --- /dev/null +++ b/libraries/spongycastle/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/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/bc/PKCS12PBEUtils.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/bc/PKCS12PBEUtils.java new file mode 100644 index 000000000..52f07a549 --- /dev/null +++ b/libraries/spongycastle/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/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcaPKCS10CertificationRequest.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcaPKCS10CertificationRequest.java new file mode 100644 index 000000000..3e4e6dc0e --- /dev/null +++ b/libraries/spongycastle/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.DefaultJcaJceHelper; +import org.spongycastle.jcajce.JcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.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/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcaPKCS10CertificationRequestBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcaPKCS10CertificationRequestBuilder.java new file mode 100644 index 000000000..0efa5fa20 --- /dev/null +++ b/libraries/spongycastle/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/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcaPKCS12SafeBagBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcaPKCS12SafeBagBuilder.java new file mode 100644 index 000000000..f8c06f7ca --- /dev/null +++ b/libraries/spongycastle/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/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcaPKCS8EncryptedPrivateKeyInfoBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcaPKCS8EncryptedPrivateKeyInfoBuilder.java new file mode 100644 index 000000000..f8a5856c8 --- /dev/null +++ b/libraries/spongycastle/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/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcePKCS12MacCalculatorBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcePKCS12MacCalculatorBuilder.java new file mode 100644 index 000000000..4e6b39f0f --- /dev/null +++ b/libraries/spongycastle/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.DefaultJcaJceHelper; +import org.spongycastle.jcajce.JcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.ProviderJcaJceHelper; +import org.spongycastle.jcajce.io.MacOutputStream; +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/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcePKCS12MacCalculatorBuilderProvider.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcePKCS12MacCalculatorBuilderProvider.java new file mode 100644 index 000000000..413afeec8 --- /dev/null +++ b/libraries/spongycastle/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.DefaultJcaJceHelper; +import org.spongycastle.jcajce.JcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.ProviderJcaJceHelper; +import org.spongycastle.jcajce.io.MacOutputStream; +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/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcePKCSPBEInputDecryptorProviderBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcePKCSPBEInputDecryptorProviderBuilder.java new file mode 100644 index 000000000..846417382 --- /dev/null +++ b/libraries/spongycastle/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.DefaultJcaJceHelper; +import org.spongycastle.jcajce.JcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.ProviderJcaJceHelper; +import org.spongycastle.jcajce.provider.symmetric.util.BCPBEKey; +import org.spongycastle.jcajce.spec.GOST28147ParameterSpec; +import org.spongycastle.jcajce.spec.PBKDF2KeySpec; +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/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcePKCSPBEOutputEncryptorBuilder.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/pkcs/jcajce/JcePKCSPBEOutputEncryptorBuilder.java new file mode 100644 index 000000000..ebf49c5e3 --- /dev/null +++ b/libraries/spongycastle/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.DefaultJcaJceHelper; +import org.spongycastle.jcajce.JcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.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); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/GenTimeAccuracy.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/GenTimeAccuracy.java new file mode 100644 index 000000000..0932f3eec --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/GenTimeAccuracy.java @@ -0,0 +1,60 @@ +package org.spongycastle.tsp; + +import org.spongycastle.asn1.DERInteger; +import org.spongycastle.asn1.tsp.Accuracy; + +public class GenTimeAccuracy +{ + private Accuracy accuracy; + + public GenTimeAccuracy(Accuracy accuracy) + { + this.accuracy = accuracy; + } + + public int getSeconds() + { + return getTimeComponent(accuracy.getSeconds()); + } + + public int getMillis() + { + return getTimeComponent(accuracy.getMillis()); + } + + public int getMicros() + { + return getTimeComponent(accuracy.getMicros()); + } + + private int getTimeComponent( + DERInteger time) + { + if (time != null) + { + return time.getValue().intValue(); + } + + return 0; + } + + public String toString() + { // digits + return getSeconds() + "." + format(getMillis()) + format(getMicros()); + } + + private String format(int v) + { + if (v < 10) + { + return "00" + v; + } + + if (v < 100) + { + return "0" + v; + } + + return Integer.toString(v); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TSPAlgorithms.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TSPAlgorithms.java new file mode 100644 index 000000000..fa5c6f064 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TSPAlgorithms.java @@ -0,0 +1,35 @@ +package org.spongycastle.tsp; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.cryptopro.CryptoProObjectIdentifiers; +import org.spongycastle.asn1.nist.NISTObjectIdentifiers; +import org.spongycastle.asn1.oiw.OIWObjectIdentifiers; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.teletrust.TeleTrusTObjectIdentifiers; + +/** + * Recognised hash algorithms for the time stamp protocol. + */ +public interface TSPAlgorithms +{ + public static final ASN1ObjectIdentifier MD5 = PKCSObjectIdentifiers.md5; + + public static final ASN1ObjectIdentifier SHA1 = OIWObjectIdentifiers.idSHA1; + + public static final ASN1ObjectIdentifier SHA224 = NISTObjectIdentifiers.id_sha224; + public static final ASN1ObjectIdentifier SHA256 = NISTObjectIdentifiers.id_sha256; + public static final ASN1ObjectIdentifier SHA384 = NISTObjectIdentifiers.id_sha384; + public static final ASN1ObjectIdentifier SHA512 = NISTObjectIdentifiers.id_sha512; + + public static final ASN1ObjectIdentifier RIPEMD128 = TeleTrusTObjectIdentifiers.ripemd128; + public static final ASN1ObjectIdentifier RIPEMD160 = TeleTrusTObjectIdentifiers.ripemd160; + public static final ASN1ObjectIdentifier RIPEMD256 = TeleTrusTObjectIdentifiers.ripemd256; + + public static final ASN1ObjectIdentifier GOST3411 = CryptoProObjectIdentifiers.gostR3411; + + public static final Set ALLOWED = new HashSet(Arrays.asList(new ASN1ObjectIdentifier[] { GOST3411, MD5, SHA1, SHA224, SHA256, SHA384, SHA512, RIPEMD128, RIPEMD160, RIPEMD256 })); +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TSPException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TSPException.java new file mode 100644 index 000000000..6125ceff3 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TSPException.java @@ -0,0 +1,28 @@ +package org.spongycastle.tsp; + +public class TSPException + extends Exception +{ + Throwable underlyingException; + + public TSPException(String message) + { + super(message); + } + + public TSPException(String message, Throwable e) + { + super(message); + underlyingException = e; + } + + public Exception getUnderlyingException() + { + return (Exception)underlyingException; + } + + public Throwable getCause() + { + return underlyingException; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TSPIOException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TSPIOException.java new file mode 100644 index 000000000..418ff1b2c --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TSPIOException.java @@ -0,0 +1,30 @@ +package org.spongycastle.tsp; + +import java.io.IOException; + +public class TSPIOException + extends IOException +{ + Throwable underlyingException; + + public TSPIOException(String message) + { + super(message); + } + + public TSPIOException(String message, Throwable e) + { + super(message); + underlyingException = e; + } + + public Exception getUnderlyingException() + { + return (Exception)underlyingException; + } + + public Throwable getCause() + { + return underlyingException; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TSPUtil.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TSPUtil.java new file mode 100644 index 000000000..23a0454c6 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TSPUtil.java @@ -0,0 +1,209 @@ +package org.spongycastle.tsp; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1Set; +import org.spongycastle.asn1.cms.Attribute; +import org.spongycastle.asn1.cms.AttributeTable; +import org.spongycastle.asn1.cms.ContentInfo; +import org.spongycastle.asn1.cryptopro.CryptoProObjectIdentifiers; +import org.spongycastle.asn1.nist.NISTObjectIdentifiers; +import org.spongycastle.asn1.oiw.OIWObjectIdentifiers; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.teletrust.TeleTrusTObjectIdentifiers; +import org.spongycastle.asn1.x509.ExtendedKeyUsage; +import org.spongycastle.asn1.x509.Extension; +import org.spongycastle.asn1.x509.Extensions; +import org.spongycastle.asn1.x509.ExtensionsGenerator; +import org.spongycastle.asn1.x509.KeyPurposeId; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.cms.SignerInformation; +import org.spongycastle.operator.DigestCalculator; +import org.spongycastle.operator.DigestCalculatorProvider; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.util.Arrays; +import org.spongycastle.util.Integers; + +public class TSPUtil +{ + private static List EMPTY_LIST = Collections.unmodifiableList(new ArrayList()); + + private static final Map digestLengths = new HashMap(); + private static final Map digestNames = new HashMap(); + + static + { + digestLengths.put(PKCSObjectIdentifiers.md5.getId(), Integers.valueOf(16)); + digestLengths.put(OIWObjectIdentifiers.idSHA1.getId(), Integers.valueOf(20)); + digestLengths.put(NISTObjectIdentifiers.id_sha224.getId(), Integers.valueOf(28)); + digestLengths.put(NISTObjectIdentifiers.id_sha256.getId(), Integers.valueOf(32)); + digestLengths.put(NISTObjectIdentifiers.id_sha384.getId(), Integers.valueOf(48)); + digestLengths.put(NISTObjectIdentifiers.id_sha512.getId(), Integers.valueOf(64)); + digestLengths.put(TeleTrusTObjectIdentifiers.ripemd128.getId(), Integers.valueOf(16)); + digestLengths.put(TeleTrusTObjectIdentifiers.ripemd160.getId(), Integers.valueOf(20)); + digestLengths.put(TeleTrusTObjectIdentifiers.ripemd256.getId(), Integers.valueOf(32)); + digestLengths.put(CryptoProObjectIdentifiers.gostR3411.getId(), Integers.valueOf(32)); + + digestNames.put(PKCSObjectIdentifiers.md5.getId(), "MD5"); + digestNames.put(OIWObjectIdentifiers.idSHA1.getId(), "SHA1"); + digestNames.put(NISTObjectIdentifiers.id_sha224.getId(), "SHA224"); + digestNames.put(NISTObjectIdentifiers.id_sha256.getId(), "SHA256"); + digestNames.put(NISTObjectIdentifiers.id_sha384.getId(), "SHA384"); + digestNames.put(NISTObjectIdentifiers.id_sha512.getId(), "SHA512"); + digestNames.put(PKCSObjectIdentifiers.sha1WithRSAEncryption.getId(), "SHA1"); + digestNames.put(PKCSObjectIdentifiers.sha224WithRSAEncryption.getId(), "SHA224"); + digestNames.put(PKCSObjectIdentifiers.sha256WithRSAEncryption.getId(), "SHA256"); + digestNames.put(PKCSObjectIdentifiers.sha384WithRSAEncryption.getId(), "SHA384"); + digestNames.put(PKCSObjectIdentifiers.sha512WithRSAEncryption.getId(), "SHA512"); + digestNames.put(TeleTrusTObjectIdentifiers.ripemd128.getId(), "RIPEMD128"); + digestNames.put(TeleTrusTObjectIdentifiers.ripemd160.getId(), "RIPEMD160"); + digestNames.put(TeleTrusTObjectIdentifiers.ripemd256.getId(), "RIPEMD256"); + digestNames.put(CryptoProObjectIdentifiers.gostR3411.getId(), "GOST3411"); + } + + /** + * Fetches the signature time-stamp attributes from a SignerInformation object. + * Checks that the MessageImprint for each time-stamp matches the signature field. + * (see RFC 3161 Appendix A). + * + * @param signerInfo a SignerInformation to search for time-stamps + * @param digCalcProvider provider for digest calculators + * @return a collection of TimeStampToken objects + * @throws TSPValidationException + */ + public static Collection getSignatureTimestamps(SignerInformation signerInfo, DigestCalculatorProvider digCalcProvider) + throws TSPValidationException + { + List timestamps = new ArrayList(); + + AttributeTable unsignedAttrs = signerInfo.getUnsignedAttributes(); + if (unsignedAttrs != null) + { + ASN1EncodableVector allTSAttrs = unsignedAttrs.getAll( + PKCSObjectIdentifiers.id_aa_signatureTimeStampToken); + for (int i = 0; i < allTSAttrs.size(); ++i) + { + Attribute tsAttr = (Attribute)allTSAttrs.get(i); + ASN1Set tsAttrValues = tsAttr.getAttrValues(); + for (int j = 0; j < tsAttrValues.size(); ++j) + { + try + { + ContentInfo contentInfo = ContentInfo.getInstance(tsAttrValues.getObjectAt(j)); + TimeStampToken timeStampToken = new TimeStampToken(contentInfo); + TimeStampTokenInfo tstInfo = timeStampToken.getTimeStampInfo(); + + DigestCalculator digCalc = digCalcProvider.get(tstInfo.getHashAlgorithm()); + + OutputStream dOut = digCalc.getOutputStream(); + + dOut.write(signerInfo.getSignature()); + dOut.close(); + + byte[] expectedDigest = digCalc.getDigest(); + + if (!Arrays.constantTimeAreEqual(expectedDigest, tstInfo.getMessageImprintDigest())) + { + throw new TSPValidationException("Incorrect digest in message imprint"); + } + + timestamps.add(timeStampToken); + } + catch (OperatorCreationException e) + { + throw new TSPValidationException("Unknown hash algorithm specified in timestamp"); + } + catch (Exception e) + { + throw new TSPValidationException("Timestamp could not be parsed"); + } + } + } + } + + return timestamps; + } + + /** + * Validate the passed in certificate as being of the correct type to be used + * for time stamping. To be valid it must have an ExtendedKeyUsage extension + * which has a key purpose identifier of id-kp-timeStamping. + * + * @param cert the certificate of interest. + * @throws TSPValidationException if the certificate fails on one of the check points. + */ + public static void validateCertificate( + X509CertificateHolder cert) + throws TSPValidationException + { + if (cert.toASN1Structure().getVersionNumber() != 3) + { + throw new IllegalArgumentException("Certificate must have an ExtendedKeyUsage extension."); + } + + Extension ext = cert.getExtension(Extension.extendedKeyUsage); + if (ext == null) + { + throw new TSPValidationException("Certificate must have an ExtendedKeyUsage extension."); + } + + if (!ext.isCritical()) + { + throw new TSPValidationException("Certificate must have an ExtendedKeyUsage extension marked as critical."); + } + + ExtendedKeyUsage extKey = ExtendedKeyUsage.getInstance(ext.getParsedValue()); + + if (!extKey.hasKeyPurposeId(KeyPurposeId.id_kp_timeStamping) || extKey.size() != 1) + { + throw new TSPValidationException("ExtendedKeyUsage not solely time stamping."); + } + } + + static int getDigestLength( + String digestAlgOID) + throws TSPException + { + Integer length = (Integer)digestLengths.get(digestAlgOID); + + if (length != null) + { + return length.intValue(); + } + + throw new TSPException("digest algorithm cannot be found."); + } + + static List getExtensionOIDs(Extensions extensions) + { + if (extensions == null) + { + return EMPTY_LIST; + } + + return Collections.unmodifiableList(java.util.Arrays.asList(extensions.getExtensionOIDs())); + } + + static void addExtension(ExtensionsGenerator extGenerator, ASN1ObjectIdentifier oid, boolean isCritical, ASN1Encodable value) + throws TSPIOException + { + try + { + extGenerator.addExtension(oid, isCritical, value); + } + catch (IOException e) + { + throw new TSPIOException("cannot encode extension: " + e.getMessage(), e); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TSPValidationException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TSPValidationException.java new file mode 100644 index 000000000..f89ac6c15 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TSPValidationException.java @@ -0,0 +1,34 @@ +package org.spongycastle.tsp; + +/** + * Exception thrown if a TSP request or response fails to validate. + * <p> + * If a failure code is associated with the exception it can be retrieved using + * the getFailureCode() method. + */ +public class TSPValidationException + extends TSPException +{ + private int failureCode = -1; + + public TSPValidationException(String message) + { + super(message); + } + + public TSPValidationException(String message, int failureCode) + { + super(message); + this.failureCode = failureCode; + } + + /** + * Return the failure code associated with this exception - if one is set. + * + * @return the failure code if set, -1 otherwise. + */ + public int getFailureCode() + { + return failureCode; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TimeStampRequest.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TimeStampRequest.java new file mode 100644 index 000000000..55349960e --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TimeStampRequest.java @@ -0,0 +1,268 @@ +package org.spongycastle.tsp; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import org.spongycastle.asn1.ASN1InputStream; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.DERObjectIdentifier; +import org.spongycastle.asn1.cmp.PKIFailureInfo; +import org.spongycastle.asn1.tsp.TimeStampReq; +import org.spongycastle.asn1.x509.Extension; +import org.spongycastle.asn1.x509.Extensions; + +/** + * Base class for an RFC 3161 Time Stamp Request. + */ +public class TimeStampRequest +{ + private static Set EMPTY_SET = Collections.unmodifiableSet(new HashSet()); + + private TimeStampReq req; + private Extensions extensions; + + public TimeStampRequest(TimeStampReq req) + { + this.req = req; + this.extensions = req.getExtensions(); + } + + /** + * Create a TimeStampRequest from the past in byte array. + * + * @param req byte array containing the request. + * @throws IOException if the request is malformed. + */ + public TimeStampRequest(byte[] req) + throws IOException + { + this(new ByteArrayInputStream(req)); + } + + /** + * Create a TimeStampRequest from the past in input stream. + * + * @param in input stream containing the request. + * @throws IOException if the request is malformed. + */ + public TimeStampRequest(InputStream in) + throws IOException + { + this(loadRequest(in)); + } + + private static TimeStampReq loadRequest(InputStream in) + throws IOException + { + try + { + return TimeStampReq.getInstance(new ASN1InputStream(in).readObject()); + } + catch (ClassCastException e) + { + throw new IOException("malformed request: " + e); + } + catch (IllegalArgumentException e) + { + throw new IOException("malformed request: " + e); + } + } + + public int getVersion() + { + return req.getVersion().getValue().intValue(); + } + + public ASN1ObjectIdentifier getMessageImprintAlgOID() + { + return req.getMessageImprint().getHashAlgorithm().getAlgorithm(); + } + + public byte[] getMessageImprintDigest() + { + return req.getMessageImprint().getHashedMessage(); + } + + public ASN1ObjectIdentifier getReqPolicy() + { + if (req.getReqPolicy() != null) + { + return req.getReqPolicy(); + } + else + { + return null; + } + } + + public BigInteger getNonce() + { + if (req.getNonce() != null) + { + return req.getNonce().getValue(); + } + else + { + return null; + } + } + + public boolean getCertReq() + { + if (req.getCertReq() != null) + { + return req.getCertReq().isTrue(); + } + else + { + return false; + } + } + + /** + * Validate the timestamp request, checking the digest to see if it is of an + * accepted type and whether it is of the correct length for the algorithm specified. + * + * @param algorithms a set of OIDs giving accepted algorithms. + * @param policies if non-null a set of policies OIDs we are willing to sign under. + * @param extensions if non-null a set of extensions OIDs we are willing to accept. + * @throws TSPException if the request is invalid, or processing fails. + */ + public void validate( + Set algorithms, + Set policies, + Set extensions) + throws TSPException + { + algorithms = convert(algorithms); + policies = convert(policies); + extensions = convert(extensions); + + if (!algorithms.contains(this.getMessageImprintAlgOID())) + { + throw new TSPValidationException("request contains unknown algorithm.", PKIFailureInfo.badAlg); + } + + if (policies != null && this.getReqPolicy() != null && !policies.contains(this.getReqPolicy())) + { + throw new TSPValidationException("request contains unknown policy.", PKIFailureInfo.unacceptedPolicy); + } + + if (this.getExtensions() != null && extensions != null) + { + Enumeration en = this.getExtensions().oids(); + while(en.hasMoreElements()) + { + String oid = ((DERObjectIdentifier)en.nextElement()).getId(); + if (!extensions.contains(oid)) + { + throw new TSPValidationException("request contains unknown extension.", PKIFailureInfo.unacceptedExtension); + } + } + } + + int digestLength = TSPUtil.getDigestLength(this.getMessageImprintAlgOID().getId()); + + if (digestLength != this.getMessageImprintDigest().length) + { + throw new TSPValidationException("imprint digest the wrong length.", PKIFailureInfo.badDataFormat); + } + } + + /** + * return the ASN.1 encoded representation of this object. + * @return the default ASN,1 byte encoding for the object. + */ + public byte[] getEncoded() throws IOException + { + return req.getEncoded(); + } + + Extensions getExtensions() + { + return extensions; + } + + public boolean hasExtensions() + { + return extensions != null; + } + + public Extension getExtension(ASN1ObjectIdentifier oid) + { + if (extensions != null) + { + return extensions.getExtension(oid); + } + + return null; + } + + public List getExtensionOIDs() + { + return TSPUtil.getExtensionOIDs(extensions); + } + + /** + * Returns a set of ASN1ObjectIdentifiers giving the non-critical extensions. + * @return a set of ASN1ObjectIdentifiers. + */ + public Set getNonCriticalExtensionOIDs() + { + if (extensions == null) + { + return EMPTY_SET; + } + + return Collections.unmodifiableSet(new HashSet(Arrays.asList(extensions.getNonCriticalExtensionOIDs()))); + } + + /** + * Returns a set of ASN1ObjectIdentifiers giving the critical extensions. + * @return a set of ASN1ObjectIdentifiers. + */ + public Set getCriticalExtensionOIDs() + { + if (extensions == null) + { + return EMPTY_SET; + } + + return Collections.unmodifiableSet(new HashSet(Arrays.asList(extensions.getCriticalExtensionOIDs()))); + } + + private Set convert(Set orig) + { + if (orig == null) + { + return orig; + } + + Set con = new HashSet(orig.size()); + + for (Iterator it = orig.iterator(); it.hasNext();) + { + Object o = it.next(); + + if (o instanceof String) + { + con.add(new ASN1ObjectIdentifier((String)o)); + } + else + { + con.add(o); + } + } + + return con; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TimeStampRequestGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TimeStampRequestGenerator.java new file mode 100644 index 000000000..382b366a8 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TimeStampRequestGenerator.java @@ -0,0 +1,163 @@ +package org.spongycastle.tsp; + +import java.io.IOException; +import java.math.BigInteger; + +import org.spongycastle.asn1.ASN1Boolean; +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.DERNull; +import org.spongycastle.asn1.tsp.MessageImprint; +import org.spongycastle.asn1.tsp.TimeStampReq; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.Extensions; +import org.spongycastle.asn1.x509.ExtensionsGenerator; + +/** + * Generator for RFC 3161 Time Stamp Request objects. + */ +public class TimeStampRequestGenerator +{ + private ASN1ObjectIdentifier reqPolicy; + + private ASN1Boolean certReq; + private ExtensionsGenerator extGenerator = new ExtensionsGenerator(); + + public TimeStampRequestGenerator() + { + } + + /** + * @deprecated use method taking ASN1ObjectIdentifier + * @param reqPolicy + */ + public void setReqPolicy( + String reqPolicy) + { + this.reqPolicy= new ASN1ObjectIdentifier(reqPolicy); + } + + public void setReqPolicy( + ASN1ObjectIdentifier reqPolicy) + { + this.reqPolicy= reqPolicy; + } + + public void setCertReq( + boolean certReq) + { + this.certReq = ASN1Boolean.getInstance(certReq); + } + + /** + * add a given extension field for the standard extensions tag (tag 3) + * @throws IOException + * @deprecated use method taking ASN1ObjectIdentifier + */ + public void addExtension( + String OID, + boolean critical, + ASN1Encodable value) + throws IOException + { + this.addExtension(OID, critical, value.toASN1Primitive().getEncoded()); + } + + /** + * add a given extension field for the standard extensions tag + * The value parameter becomes the contents of the octet string associated + * with the extension. + * @deprecated use method taking ASN1ObjectIdentifier + */ + public void addExtension( + String OID, + boolean critical, + byte[] value) + { + extGenerator.addExtension(new ASN1ObjectIdentifier(OID), critical, value); + } + + /** + * add a given extension field for the standard extensions tag (tag 3) + * @throws TSPIOException + */ + public void addExtension( + ASN1ObjectIdentifier oid, + boolean isCritical, + ASN1Encodable value) + throws TSPIOException + { + TSPUtil.addExtension(extGenerator, oid, isCritical, value); + } + + /** + * add a given extension field for the standard extensions tag + * The value parameter becomes the contents of the octet string associated + * with the extension. + */ + public void addExtension( + ASN1ObjectIdentifier oid, + boolean isCritical, + byte[] value) + { + extGenerator.addExtension(oid, isCritical, value); + } + + /** + * @deprecated use method taking ANS1ObjectIdentifier + */ + public TimeStampRequest generate( + String digestAlgorithm, + byte[] digest) + { + return this.generate(digestAlgorithm, digest, null); + } + + /** + * @deprecated use method taking ANS1ObjectIdentifier + */ + public TimeStampRequest generate( + String digestAlgorithmOID, + byte[] digest, + BigInteger nonce) + { + if (digestAlgorithmOID == null) + { + throw new IllegalArgumentException("No digest algorithm specified"); + } + + ASN1ObjectIdentifier digestAlgOID = new ASN1ObjectIdentifier(digestAlgorithmOID); + + AlgorithmIdentifier algID = new AlgorithmIdentifier(digestAlgOID, DERNull.INSTANCE); + MessageImprint messageImprint = new MessageImprint(algID, digest); + + Extensions ext = null; + + if (!extGenerator.isEmpty()) + { + ext = extGenerator.generate(); + } + + if (nonce != null) + { + return new TimeStampRequest(new TimeStampReq(messageImprint, + reqPolicy, new ASN1Integer(nonce), certReq, ext)); + } + else + { + return new TimeStampRequest(new TimeStampReq(messageImprint, + reqPolicy, null, certReq, ext)); + } + } + + public TimeStampRequest generate(ASN1ObjectIdentifier digestAlgorithm, byte[] digest) + { + return generate(digestAlgorithm.getId(), digest); + } + + public TimeStampRequest generate(ASN1ObjectIdentifier digestAlgorithm, byte[] digest, BigInteger nonce) + { + return generate(digestAlgorithm.getId(), digest, nonce); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TimeStampResponse.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TimeStampResponse.java new file mode 100644 index 000000000..cc327f45a --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TimeStampResponse.java @@ -0,0 +1,189 @@ +package org.spongycastle.tsp; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.spongycastle.asn1.ASN1InputStream; +import org.spongycastle.asn1.cmp.PKIFailureInfo; +import org.spongycastle.asn1.cmp.PKIFreeText; +import org.spongycastle.asn1.cmp.PKIStatus; +import org.spongycastle.asn1.cms.Attribute; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.tsp.TimeStampResp; +import org.spongycastle.util.Arrays; + +/** + * Base class for an RFC 3161 Time Stamp Response object. + */ +public class TimeStampResponse +{ + TimeStampResp resp; + TimeStampToken timeStampToken; + + public TimeStampResponse(TimeStampResp resp) + throws TSPException, IOException + { + this.resp = resp; + + if (resp.getTimeStampToken() != null) + { + timeStampToken = new TimeStampToken(resp.getTimeStampToken()); + } + } + + /** + * Create a TimeStampResponse from a byte array containing an ASN.1 encoding. + * + * @param resp the byte array containing the encoded response. + * @throws TSPException if the response is malformed. + * @throws IOException if the byte array doesn't represent an ASN.1 encoding. + */ + public TimeStampResponse(byte[] resp) + throws TSPException, IOException + { + this(new ByteArrayInputStream(resp)); + } + + /** + * Create a TimeStampResponse from an input stream containing an ASN.1 encoding. + * + * @param in the input stream containing the encoded response. + * @throws TSPException if the response is malformed. + * @throws IOException if the stream doesn't represent an ASN.1 encoding. + */ + public TimeStampResponse(InputStream in) + throws TSPException, IOException + { + this(readTimeStampResp(in)); + } + + private static TimeStampResp readTimeStampResp( + InputStream in) + throws IOException, TSPException + { + try + { + return TimeStampResp.getInstance(new ASN1InputStream(in).readObject()); + } + catch (IllegalArgumentException e) + { + throw new TSPException("malformed timestamp response: " + e, e); + } + catch (ClassCastException e) + { + throw new TSPException("malformed timestamp response: " + e, e); + } + } + + public int getStatus() + { + return resp.getStatus().getStatus().intValue(); + } + + public String getStatusString() + { + if (resp.getStatus().getStatusString() != null) + { + StringBuffer statusStringBuf = new StringBuffer(); + PKIFreeText text = resp.getStatus().getStatusString(); + for (int i = 0; i != text.size(); i++) + { + statusStringBuf.append(text.getStringAt(i).getString()); + } + return statusStringBuf.toString(); + } + else + { + return null; + } + } + + public PKIFailureInfo getFailInfo() + { + if (resp.getStatus().getFailInfo() != null) + { + return new PKIFailureInfo(resp.getStatus().getFailInfo()); + } + + return null; + } + + public TimeStampToken getTimeStampToken() + { + return timeStampToken; + } + + /** + * Check this response against to see if it a well formed response for + * the passed in request. Validation will include checking the time stamp + * token if the response status is GRANTED or GRANTED_WITH_MODS. + * + * @param request the request to be checked against + * @throws TSPException if the request can not match this response. + */ + public void validate( + TimeStampRequest request) + throws TSPException + { + TimeStampToken tok = this.getTimeStampToken(); + + if (tok != null) + { + TimeStampTokenInfo tstInfo = tok.getTimeStampInfo(); + + if (request.getNonce() != null && !request.getNonce().equals(tstInfo.getNonce())) + { + throw new TSPValidationException("response contains wrong nonce value."); + } + + if (this.getStatus() != PKIStatus.GRANTED && this.getStatus() != PKIStatus.GRANTED_WITH_MODS) + { + throw new TSPValidationException("time stamp token found in failed request."); + } + + if (!Arrays.constantTimeAreEqual(request.getMessageImprintDigest(), tstInfo.getMessageImprintDigest())) + { + throw new TSPValidationException("response for different message imprint digest."); + } + + if (!tstInfo.getMessageImprintAlgOID().equals(request.getMessageImprintAlgOID())) + { + throw new TSPValidationException("response for different message imprint algorithm."); + } + + Attribute scV1 = tok.getSignedAttributes().get(PKCSObjectIdentifiers.id_aa_signingCertificate); + Attribute scV2 = tok.getSignedAttributes().get(PKCSObjectIdentifiers.id_aa_signingCertificateV2); + + if (scV1 == null && scV2 == null) + { + throw new TSPValidationException("no signing certificate attribute present."); + } + + if (scV1 != null && scV2 != null) + { + /* + * RFC 5035 5.4. If both attributes exist in a single message, + * they are independently evaluated. + */ + } + + if (request.getReqPolicy() != null && !request.getReqPolicy().equals(tstInfo.getPolicy())) + { + throw new TSPValidationException("TSA policy wrong for request."); + } + } + else if (this.getStatus() == PKIStatus.GRANTED || this.getStatus() == PKIStatus.GRANTED_WITH_MODS) + { + throw new TSPValidationException("no time stamp token found and one expected."); + } + } + + /** + * return the ASN.1 encoded representation of this object. + */ + public byte[] getEncoded() throws IOException + { + return resp.getEncoded(); + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TimeStampResponseGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TimeStampResponseGenerator.java new file mode 100644 index 000000000..efe08a50a --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TimeStampResponseGenerator.java @@ -0,0 +1,353 @@ +package org.spongycastle.tsp; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.DERBitString; +import org.spongycastle.asn1.DERInteger; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.asn1.DERUTF8String; +import org.spongycastle.asn1.cmp.PKIFailureInfo; +import org.spongycastle.asn1.cmp.PKIFreeText; +import org.spongycastle.asn1.cmp.PKIStatus; +import org.spongycastle.asn1.cmp.PKIStatusInfo; +import org.spongycastle.asn1.cms.ContentInfo; +import org.spongycastle.asn1.tsp.TimeStampResp; + +/** + * Generator for RFC 3161 Time Stamp Responses. + * <p> + * New generate methods have been introduced to give people more control over what ends up in the message. + * Unfortunately it turns out that in some cases fields like statusString must be left out otherwise a an + * otherwise valid timestamp will be rejected. + * </p> + * If you're after the most control with generating a response use: + * <pre> + * TimeStampResponse tsResp; + * + * try + * { + * tsResp = tsRespGen.generateGrantedResponse(request, new BigInteger("23"), new Date()); + * } + * catch (Exception e) + * { + * tsResp = tsRespGen.generateRejectedResponse(e); + * } + * </pre> + * The generate method does this, but provides a status string of "Operation Okay". + * <p> + * It should be pointed out that generateRejectedResponse() may also, on very rare occasions throw a TSPException. + * In the event that happens, there's a serious internal problem with your responder. + * </p> + */ +public class TimeStampResponseGenerator +{ + int status; + + ASN1EncodableVector statusStrings; + + int failInfo; + private TimeStampTokenGenerator tokenGenerator; + private Set acceptedAlgorithms; + private Set acceptedPolicies; + private Set acceptedExtensions; + + /** + * + * @param tokenGenerator + * @param acceptedAlgorithms a set of OIDs giving accepted algorithms. + */ + public TimeStampResponseGenerator( + TimeStampTokenGenerator tokenGenerator, + Set acceptedAlgorithms) + { + this(tokenGenerator, acceptedAlgorithms, null, null); + } + + /** + * + * @param tokenGenerator + * @param acceptedAlgorithms a set of OIDs giving accepted algorithms. + * @param acceptedPolicies if non-null a set of policies OIDs we are willing to sign under. + */ + public TimeStampResponseGenerator( + TimeStampTokenGenerator tokenGenerator, + Set acceptedAlgorithms, + Set acceptedPolicies) + { + this(tokenGenerator, acceptedAlgorithms, acceptedPolicies, null); + } + + /** + * + * @param tokenGenerator + * @param acceptedAlgorithms a set of OIDs giving accepted algorithms. + * @param acceptedPolicies if non-null a set of policies OIDs we are willing to sign under. + * @param acceptedExtensions if non-null a set of extensions OIDs we are willing to accept. + */ + public TimeStampResponseGenerator( + TimeStampTokenGenerator tokenGenerator, + Set acceptedAlgorithms, + Set acceptedPolicies, + Set acceptedExtensions) + { + this.tokenGenerator = tokenGenerator; + this.acceptedAlgorithms = convert(acceptedAlgorithms); + this.acceptedPolicies = convert(acceptedPolicies); + this.acceptedExtensions = convert(acceptedExtensions); + + statusStrings = new ASN1EncodableVector(); + } + + private void addStatusString(String statusString) + { + statusStrings.add(new DERUTF8String(statusString)); + } + + private void setFailInfoField(int field) + { + failInfo = failInfo | field; + } + + private PKIStatusInfo getPKIStatusInfo() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(new DERInteger(status)); + + if (statusStrings.size() > 0) + { + v.add(PKIFreeText.getInstance(new DERSequence(statusStrings))); + } + + if (failInfo != 0) + { + DERBitString failInfoBitString = new FailInfo(failInfo); + v.add(failInfoBitString); + } + + return PKIStatusInfo.getInstance(new DERSequence(v)); + } + + /** + * Return an appropriate TimeStampResponse. + * <p> + * If genTime is null a timeNotAvailable error response will be returned. Calling generate() is the + * equivalent of: + * <pre> + * TimeStampResponse tsResp; + * + * try + * { + * tsResp = tsRespGen.generateGrantedResponse(request, serialNumber, genTime, "Operation Okay"); + * } + * catch (Exception e) + * { + * tsResp = tsRespGen.generateRejectedResponse(e); + * } + * </pre> + * @param request the request this response is for. + * @param serialNumber serial number for the response token. + * @param genTime generation time for the response token. + * @return a TimeStampResponse. + * @throws TSPException + */ + public TimeStampResponse generate( + TimeStampRequest request, + BigInteger serialNumber, + Date genTime) + throws TSPException + { + try + { + return this.generateGrantedResponse(request, serialNumber, genTime, "Operation Okay"); + } + catch (Exception e) + { + return this.generateRejectedResponse(e); + } + } + + /** + * Return a granted response, if the passed in request passes validation. + * <p> + * If genTime is null a timeNotAvailable or a validation exception occurs a TSPValidationException will + * be thrown. The parent TSPException will only occur on some sort of system failure. + * </p> + * @param request the request this response is for. + * @param serialNumber serial number for the response token. + * @param genTime generation time for the response token. + * @return the TimeStampResponse with a status of PKIStatus.GRANTED + * @throws TSPException on validation exception or internal error. + */ + public TimeStampResponse generateGrantedResponse( + TimeStampRequest request, + BigInteger serialNumber, + Date genTime) + throws TSPException + { + return generateGrantedResponse(request, serialNumber, genTime, null); + } + + /** + * Return a granted response, if the passed in request passes validation with the passed in status string. + * <p> + * If genTime is null a timeNotAvailable or a validation exception occurs a TSPValidationException will + * be thrown. The parent TSPException will only occur on some sort of system failure. + * </p> + * @param request the request this response is for. + * @param serialNumber serial number for the response token. + * @param genTime generation time for the response token. + * @return the TimeStampResponse with a status of PKIStatus.GRANTED + * @throws TSPException on validation exception or internal error. + */ + public TimeStampResponse generateGrantedResponse( + TimeStampRequest request, + BigInteger serialNumber, + Date genTime, + String statusString) + throws TSPException + { + if (genTime == null) + { + throw new TSPValidationException("The time source is not available.", PKIFailureInfo.timeNotAvailable); + } + + request.validate(acceptedAlgorithms, acceptedPolicies, acceptedExtensions); + + status = PKIStatus.GRANTED; + statusStrings = new ASN1EncodableVector(); + + if (statusString != null) + { + this.addStatusString(statusString); + } + + PKIStatusInfo pkiStatusInfo = getPKIStatusInfo(); + + ContentInfo tstTokenContentInfo; + try + { + tstTokenContentInfo = tokenGenerator.generate(request, serialNumber, genTime).toCMSSignedData().toASN1Structure(); + } + catch (TSPException e) + { + throw e; + } + catch (Exception e) + { + throw new TSPException( + "Timestamp token received cannot be converted to ContentInfo", e); + } + + TimeStampResp resp = new TimeStampResp(pkiStatusInfo, tstTokenContentInfo); + + try + { + return new TimeStampResponse(resp); + } + catch (IOException e) + { + throw new TSPException("created badly formatted response!"); + } + } + + /** + * Generate a generic rejection response based on a TSPValidationException or + * an Exception. Exceptions which are not an instance of TSPValidationException + * will be treated as systemFailure. The return value of exception.getMessage() will + * be used as the status string for the response. + * + * @param exception the exception thrown on validating the request. + * @return a TimeStampResponse. + * @throws TSPException if a failure response cannot be generated. + */ + public TimeStampResponse generateRejectedResponse(Exception exception) + throws TSPException + { + if (exception instanceof TSPValidationException) + { + return generateFailResponse(PKIStatus.REJECTION, ((TSPValidationException)exception).getFailureCode(), exception.getMessage()); + } + else + { + return generateFailResponse(PKIStatus.REJECTION, PKIFailureInfo.systemFailure, exception.getMessage()); + } + } + + /** + * Generate a non-granted TimeStampResponse with chosen status and FailInfoField. + * + * @param status the PKIStatus to set. + * @param failInfoField the FailInfoField to set. + * @param statusString an optional string describing the failure. + * @return a TimeStampResponse with a failInfoField and optional statusString + * @throws TSPException in case the response could not be created + */ + public TimeStampResponse generateFailResponse(int status, int failInfoField, String statusString) + throws TSPException + { + this.status = status; + this.statusStrings = new ASN1EncodableVector(); + + this.setFailInfoField(failInfoField); + + if (statusString != null) + { + this.addStatusString(statusString); + } + + PKIStatusInfo pkiStatusInfo = getPKIStatusInfo(); + + TimeStampResp resp = new TimeStampResp(pkiStatusInfo, null); + + try + { + return new TimeStampResponse(resp); + } + catch (IOException e) + { + throw new TSPException("created badly formatted response!"); + } + } + + private Set convert(Set orig) + { + if (orig == null) + { + return orig; + } + + Set con = new HashSet(orig.size()); + + for (Iterator it = orig.iterator(); it.hasNext();) + { + Object o = it.next(); + + if (o instanceof String) + { + con.add(new ASN1ObjectIdentifier((String)o)); + } + else + { + con.add(o); + } + } + + return con; + } + + class FailInfo extends DERBitString + { + FailInfo(int failInfoValue) + { + super(getBytes(failInfoValue), getPadBits(failInfoValue)); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TimeStampToken.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TimeStampToken.java new file mode 100644 index 000000000..00d2a903b --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TimeStampToken.java @@ -0,0 +1,393 @@ +package org.spongycastle.tsp; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collection; +import java.util.Date; + +import org.spongycastle.asn1.ASN1InputStream; +import org.spongycastle.asn1.cms.Attribute; +import org.spongycastle.asn1.cms.AttributeTable; +import org.spongycastle.asn1.cms.ContentInfo; +import org.spongycastle.asn1.cms.IssuerAndSerialNumber; +import org.spongycastle.asn1.ess.ESSCertID; +import org.spongycastle.asn1.ess.ESSCertIDv2; +import org.spongycastle.asn1.ess.SigningCertificate; +import org.spongycastle.asn1.ess.SigningCertificateV2; +import org.spongycastle.asn1.nist.NISTObjectIdentifiers; +import org.spongycastle.asn1.oiw.OIWObjectIdentifiers; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.tsp.TSTInfo; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.GeneralName; +import org.spongycastle.asn1.x509.IssuerSerial; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.cms.CMSException; +import org.spongycastle.cms.CMSProcessable; +import org.spongycastle.cms.CMSSignedData; +import org.spongycastle.cms.SignerId; +import org.spongycastle.cms.SignerInformation; +import org.spongycastle.cms.SignerInformationVerifier; +import org.spongycastle.operator.DigestCalculator; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.util.Arrays; +import org.spongycastle.util.Store; + +/** + * Carrier class for a TimeStampToken. + */ +public class TimeStampToken +{ + CMSSignedData tsToken; + + SignerInformation tsaSignerInfo; + + Date genTime; + + TimeStampTokenInfo tstInfo; + + CertID certID; + + public TimeStampToken(ContentInfo contentInfo) + throws TSPException, IOException + { + this(getSignedData(contentInfo)); + } + + private static CMSSignedData getSignedData(ContentInfo contentInfo) + throws TSPException + { + try + { + return new CMSSignedData(contentInfo); + } + catch (CMSException e) + { + throw new TSPException("TSP parsing error: " + e.getMessage(), e.getCause()); + } + } + + public TimeStampToken(CMSSignedData signedData) + throws TSPException, IOException + { + this.tsToken = signedData; + + if (!this.tsToken.getSignedContentTypeOID().equals(PKCSObjectIdentifiers.id_ct_TSTInfo.getId())) + { + throw new TSPValidationException("ContentInfo object not for a time stamp."); + } + + Collection signers = tsToken.getSignerInfos().getSigners(); + + if (signers.size() != 1) + { + throw new IllegalArgumentException("Time-stamp token signed by " + + signers.size() + + " signers, but it must contain just the TSA signature."); + } + + tsaSignerInfo = (SignerInformation)signers.iterator().next(); + + try + { + CMSProcessable content = tsToken.getSignedContent(); + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + content.write(bOut); + + ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(bOut.toByteArray())); + + this.tstInfo = new TimeStampTokenInfo(TSTInfo.getInstance(aIn.readObject())); + + Attribute attr = tsaSignerInfo.getSignedAttributes().get(PKCSObjectIdentifiers.id_aa_signingCertificate); + + if (attr != null) + { + SigningCertificate signCert = SigningCertificate.getInstance(attr.getAttrValues().getObjectAt(0)); + + this.certID = new CertID(ESSCertID.getInstance(signCert.getCerts()[0])); + } + else + { + attr = tsaSignerInfo.getSignedAttributes().get(PKCSObjectIdentifiers.id_aa_signingCertificateV2); + + if (attr == null) + { + throw new TSPValidationException("no signing certificate attribute found, time stamp invalid."); + } + + SigningCertificateV2 signCertV2 = SigningCertificateV2.getInstance(attr.getAttrValues().getObjectAt(0)); + + this.certID = new CertID(ESSCertIDv2.getInstance(signCertV2.getCerts()[0])); + } + } + catch (CMSException e) + { + throw new TSPException(e.getMessage(), e.getUnderlyingException()); + } + } + + public TimeStampTokenInfo getTimeStampInfo() + { + return tstInfo; + } + + public SignerId getSID() + { + return tsaSignerInfo.getSID(); + } + + public AttributeTable getSignedAttributes() + { + return tsaSignerInfo.getSignedAttributes(); + } + + public AttributeTable getUnsignedAttributes() + { + return tsaSignerInfo.getUnsignedAttributes(); + } + + public Store getCertificates() + { + return tsToken.getCertificates(); + } + + public Store getCRLs() + { + return tsToken.getCRLs(); + } + + public Store getAttributeCertificates() + { + return tsToken.getAttributeCertificates(); + } + + /** + * Validate the time stamp token. + * <p> + * To be valid the token must be signed by the passed in certificate and + * the certificate must be the one referred to by the SigningCertificate + * attribute included in the hashed attributes of the token. The + * certificate must also have the ExtendedKeyUsageExtension with only + * KeyPurposeId.id_kp_timeStamping and have been valid at the time the + * timestamp was created. + * </p> + * <p> + * A successful call to validate means all the above are true. + * </p> + * + * @param sigVerifier the content verifier create the objects required to verify the CMS object in the timestamp. + * @throws TSPException if an exception occurs in processing the token. + * @throws TSPValidationException if the certificate or signature fail to be valid. + * @throws IllegalArgumentException if the sigVerifierProvider has no associated certificate. + */ + public void validate( + SignerInformationVerifier sigVerifier) + throws TSPException, TSPValidationException + { + if (!sigVerifier.hasAssociatedCertificate()) + { + throw new IllegalArgumentException("verifier provider needs an associated certificate"); + } + + try + { + X509CertificateHolder certHolder = sigVerifier.getAssociatedCertificate(); + DigestCalculator calc = sigVerifier.getDigestCalculator(certID.getHashAlgorithm()); + + OutputStream cOut = calc.getOutputStream(); + + cOut.write(certHolder.getEncoded()); + cOut.close(); + + if (!Arrays.constantTimeAreEqual(certID.getCertHash(), calc.getDigest())) + { + throw new TSPValidationException("certificate hash does not match certID hash."); + } + + if (certID.getIssuerSerial() != null) + { + IssuerAndSerialNumber issuerSerial = new IssuerAndSerialNumber(certHolder.toASN1Structure()); + + if (!certID.getIssuerSerial().getSerial().equals(issuerSerial.getSerialNumber())) + { + throw new TSPValidationException("certificate serial number does not match certID for signature."); + } + + GeneralName[] names = certID.getIssuerSerial().getIssuer().getNames(); + boolean found = false; + + for (int i = 0; i != names.length; i++) + { + if (names[i].getTagNo() == 4 && X500Name.getInstance(names[i].getName()).equals(X500Name.getInstance(issuerSerial.getName()))) + { + found = true; + break; + } + } + + if (!found) + { + throw new TSPValidationException("certificate name does not match certID for signature. "); + } + } + + TSPUtil.validateCertificate(certHolder); + + if (!certHolder.isValidOn(tstInfo.getGenTime())) + { + throw new TSPValidationException("certificate not valid when time stamp created."); + } + + if (!tsaSignerInfo.verify(sigVerifier)) + { + throw new TSPValidationException("signature not created by certificate."); + } + } + catch (CMSException e) + { + if (e.getUnderlyingException() != null) + { + throw new TSPException(e.getMessage(), e.getUnderlyingException()); + } + else + { + throw new TSPException("CMS exception: " + e, e); + } + } + catch (IOException e) + { + throw new TSPException("problem processing certificate: " + e, e); + } + catch (OperatorCreationException e) + { + throw new TSPException("unable to create digest: " + e.getMessage(), e); + } + } + + /** + * Return true if the signature on time stamp token is valid. + * <p> + * Note: this is a much weaker proof of correctness than calling validate(). + * </p> + * + * @param sigVerifier the content verifier create the objects required to verify the CMS object in the timestamp. + * @return true if the signature matches, false otherwise. + * @throws TSPException if the signature cannot be processed or the provider cannot match the algorithm. + */ + public boolean isSignatureValid( + SignerInformationVerifier sigVerifier) + throws TSPException + { + try + { + return tsaSignerInfo.verify(sigVerifier); + } + catch (CMSException e) + { + if (e.getUnderlyingException() != null) + { + throw new TSPException(e.getMessage(), e.getUnderlyingException()); + } + else + { + throw new TSPException("CMS exception: " + e, e); + } + } + } + + /** + * Return the underlying CMSSignedData object. + * + * @return the underlying CMS structure. + */ + public CMSSignedData toCMSSignedData() + { + return tsToken; + } + + /** + * Return a ASN.1 encoded byte stream representing the encoded object. + * + * @throws IOException if encoding fails. + */ + public byte[] getEncoded() + throws IOException + { + return tsToken.getEncoded(); + } + + // perhaps this should be done using an interface on the ASN.1 classes... + private class CertID + { + private ESSCertID certID; + private ESSCertIDv2 certIDv2; + + CertID(ESSCertID certID) + { + this.certID = certID; + this.certIDv2 = null; + } + + CertID(ESSCertIDv2 certID) + { + this.certIDv2 = certID; + this.certID = null; + } + + public String getHashAlgorithmName() + { + if (certID != null) + { + return "SHA-1"; + } + else + { + if (NISTObjectIdentifiers.id_sha256.equals(certIDv2.getHashAlgorithm().getAlgorithm())) + { + return "SHA-256"; + } + return certIDv2.getHashAlgorithm().getAlgorithm().getId(); + } + } + + public AlgorithmIdentifier getHashAlgorithm() + { + if (certID != null) + { + return new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1); + } + else + { + return certIDv2.getHashAlgorithm(); + } + } + + public byte[] getCertHash() + { + if (certID != null) + { + return certID.getCertHash(); + } + else + { + return certIDv2.getCertHash(); + } + } + + public IssuerSerial getIssuerSerial() + { + if (certID != null) + { + return certID.getIssuerSerial(); + } + else + { + return certIDv2.getIssuerSerial(); + } + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TimeStampTokenGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TimeStampTokenGenerator.java new file mode 100644 index 000000000..71b1cda91 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TimeStampTokenGenerator.java @@ -0,0 +1,356 @@ +package org.spongycastle.tsp; + +import java.io.IOException; +import java.io.OutputStream; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.spongycastle.asn1.ASN1Boolean; +import org.spongycastle.asn1.ASN1Encoding; +import org.spongycastle.asn1.ASN1GeneralizedTime; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.DERNull; +import org.spongycastle.asn1.cms.AttributeTable; +import org.spongycastle.asn1.ess.ESSCertID; +import org.spongycastle.asn1.ess.ESSCertIDv2; +import org.spongycastle.asn1.ess.SigningCertificate; +import org.spongycastle.asn1.ess.SigningCertificateV2; +import org.spongycastle.asn1.oiw.OIWObjectIdentifiers; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.tsp.Accuracy; +import org.spongycastle.asn1.tsp.MessageImprint; +import org.spongycastle.asn1.tsp.TSTInfo; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.GeneralName; +import org.spongycastle.asn1.x509.GeneralNames; +import org.spongycastle.asn1.x509.IssuerSerial; +import org.spongycastle.cert.X509CertificateHolder; +import org.spongycastle.cms.CMSAttributeTableGenerationException; +import org.spongycastle.cms.CMSAttributeTableGenerator; +import org.spongycastle.cms.CMSException; +import org.spongycastle.cms.CMSProcessableByteArray; +import org.spongycastle.cms.CMSSignedData; +import org.spongycastle.cms.CMSSignedDataGenerator; +import org.spongycastle.cms.SignerInfoGenerator; +import org.spongycastle.operator.DigestCalculator; +import org.spongycastle.util.CollectionStore; +import org.spongycastle.util.Store; + +/** + * Currently the class supports ESSCertID by if a digest calculator based on SHA1 is passed in, otherwise it uses + * ESSCertIDv2. In the event you need to pass both types, you will need to override the SignedAttributeGenerator + * for the SignerInfoGeneratorBuilder you are using. For the default for ESSCertIDv2 the code will look something + * like the following: + * <pre> + * final ESSCertID essCertid = new ESSCertID(certHashSha1, issuerSerial); + * final ESSCertIDv2 essCertidV2 = new ESSCertIDv2(certHashSha256, issuerSerial); + * + * signerInfoGenBuilder.setSignedAttributeGenerator(new CMSAttributeTableGenerator() + * { + * public AttributeTable getAttributes(Map parameters) + * throws CMSAttributeTableGenerationException + * { + * CMSAttributeTableGenerator attrGen = new DefaultSignedAttributeTableGenerator(); + * + * AttributeTable table = attrGen.getAttributes(parameters); + * + * table = table.add(PKCSObjectIdentifiers.id_aa_signingCertificate, new SigningCertificate(essCertid)); + * table = table.add(PKCSObjectIdentifiers.id_aa_signingCertificateV2, new SigningCertificateV2(essCertidV2)); + * + * return table; + * } + * }); + * </pre> + */ +public class TimeStampTokenGenerator +{ + int accuracySeconds = -1; + + int accuracyMillis = -1; + + int accuracyMicros = -1; + + boolean ordering = false; + + GeneralName tsa = null; + + private ASN1ObjectIdentifier tsaPolicyOID; + + private List certs = new ArrayList(); + private List crls = new ArrayList(); + private List attrCerts = new ArrayList(); + private SignerInfoGenerator signerInfoGen; + + /** + * Basic Constructor - set up a calculator based on signerInfoGen with a ESSCertID calculated from + * the signer's associated certificate using the sha1DigestCalculator. If alternate values are required + * for id-aa-signingCertificate they should be added to the signerInfoGen object before it is passed in, + * otherwise a standard digest based value will be added. + * + * @param signerInfoGen the generator for the signer we are using. + * @param digestCalculator calculator for to use for digest of certificate. + * @param tsaPolicy tasPolicy to send. + * @throws IllegalArgumentException if calculator is not SHA-1 or there is no associated certificate for the signer, + * @throws TSPException if the signer certificate cannot be processed. + */ + public TimeStampTokenGenerator( + final SignerInfoGenerator signerInfoGen, + DigestCalculator digestCalculator, + ASN1ObjectIdentifier tsaPolicy) + throws IllegalArgumentException, TSPException + { + this(signerInfoGen, digestCalculator, tsaPolicy, false); + } + + /** + * Basic Constructor - set up a calculator based on signerInfoGen with a ESSCertID calculated from + * the signer's associated certificate using the sha1DigestCalculator. If alternate values are required + * for id-aa-signingCertificate they should be added to the signerInfoGen object before it is passed in, + * otherwise a standard digest based value will be added. + * + * @param signerInfoGen the generator for the signer we are using. + * @param digestCalculator calculator for to use for digest of certificate. + * @param tsaPolicy tasPolicy to send. + * @param isIssuerSerialIncluded should issuerSerial be included in the ESSCertIDs, true if yes, by default false. + * @throws IllegalArgumentException if calculator is not SHA-1 or there is no associated certificate for the signer, + * @throws TSPException if the signer certificate cannot be processed. + */ + public TimeStampTokenGenerator( + final SignerInfoGenerator signerInfoGen, + DigestCalculator digestCalculator, + ASN1ObjectIdentifier tsaPolicy, + boolean isIssuerSerialIncluded) + throws IllegalArgumentException, TSPException + { + this.signerInfoGen = signerInfoGen; + this.tsaPolicyOID = tsaPolicy; + + if (!signerInfoGen.hasAssociatedCertificate()) + { + throw new IllegalArgumentException("SignerInfoGenerator must have an associated certificate"); + } + + X509CertificateHolder assocCert = signerInfoGen.getAssociatedCertificate(); + TSPUtil.validateCertificate(assocCert); + + try + { + OutputStream dOut = digestCalculator.getOutputStream(); + + dOut.write(assocCert.getEncoded()); + + dOut.close(); + + if (digestCalculator.getAlgorithmIdentifier().getAlgorithm().equals(OIWObjectIdentifiers.idSHA1)) + { + final ESSCertID essCertid = new ESSCertID(digestCalculator.getDigest(), + isIssuerSerialIncluded ? new IssuerSerial(new GeneralNames(new GeneralName(assocCert.getIssuer())), assocCert.getSerialNumber()) + : null); + + this.signerInfoGen = new SignerInfoGenerator(signerInfoGen, new CMSAttributeTableGenerator() + { + public AttributeTable getAttributes(Map parameters) + throws CMSAttributeTableGenerationException + { + AttributeTable table = signerInfoGen.getSignedAttributeTableGenerator().getAttributes(parameters); + + if (table.get(PKCSObjectIdentifiers.id_aa_signingCertificate) == null) + { + return table.add(PKCSObjectIdentifiers.id_aa_signingCertificate, new SigningCertificate(essCertid)); + } + + return table; + } + }, signerInfoGen.getUnsignedAttributeTableGenerator()); + } + else + { + AlgorithmIdentifier digAlgID = new AlgorithmIdentifier(digestCalculator.getAlgorithmIdentifier().getAlgorithm()); + final ESSCertIDv2 essCertid = new ESSCertIDv2(digAlgID, digestCalculator.getDigest(), + isIssuerSerialIncluded ? new IssuerSerial(new GeneralNames(new GeneralName(assocCert.getIssuer())), new ASN1Integer(assocCert.getSerialNumber())) + : null); + + this.signerInfoGen = new SignerInfoGenerator(signerInfoGen, new CMSAttributeTableGenerator() + { + public AttributeTable getAttributes(Map parameters) + throws CMSAttributeTableGenerationException + { + AttributeTable table = signerInfoGen.getSignedAttributeTableGenerator().getAttributes(parameters); + + if (table.get(PKCSObjectIdentifiers.id_aa_signingCertificateV2) == null) + { + return table.add(PKCSObjectIdentifiers.id_aa_signingCertificateV2, new SigningCertificateV2(essCertid)); + } + + return table; + } + }, signerInfoGen.getUnsignedAttributeTableGenerator()); + } + } + catch (IOException e) + { + throw new TSPException("Exception processing certificate.", e); + } + } + + /** + * Add the store of X509 Certificates to the generator. + * + * @param certStore a Store containing X509CertificateHolder objects + */ + public void addCertificates( + Store certStore) + { + certs.addAll(certStore.getMatches(null)); + } + + /** + * + * @param crlStore a Store containing X509CRLHolder objects. + */ + public void addCRLs( + Store crlStore) + { + crls.addAll(crlStore.getMatches(null)); + } + + /** + * + * @param attrStore a Store containing X509AttributeCertificate objects. + */ + public void addAttributeCertificates( + Store attrStore) + { + attrCerts.addAll(attrStore.getMatches(null)); + } + + public void setAccuracySeconds(int accuracySeconds) + { + this.accuracySeconds = accuracySeconds; + } + + public void setAccuracyMillis(int accuracyMillis) + { + this.accuracyMillis = accuracyMillis; + } + + public void setAccuracyMicros(int accuracyMicros) + { + this.accuracyMicros = accuracyMicros; + } + + public void setOrdering(boolean ordering) + { + this.ordering = ordering; + } + + public void setTSA(GeneralName tsa) + { + this.tsa = tsa; + } + + /** + * Generate a TimeStampToken for the passed in request and serialNumber marking it with the passed in genTime. + * + * @param request the originating request. + * @param serialNumber serial number for the TimeStampToken + * @param genTime token generation time. + * @return a TimeStampToken + * @throws TSPException + */ + public TimeStampToken generate( + TimeStampRequest request, + BigInteger serialNumber, + Date genTime) + throws TSPException + { + ASN1ObjectIdentifier digestAlgOID = request.getMessageImprintAlgOID(); + + AlgorithmIdentifier algID = new AlgorithmIdentifier(digestAlgOID, DERNull.INSTANCE); + MessageImprint messageImprint = new MessageImprint(algID, request.getMessageImprintDigest()); + + Accuracy accuracy = null; + if (accuracySeconds > 0 || accuracyMillis > 0 || accuracyMicros > 0) + { + ASN1Integer seconds = null; + if (accuracySeconds > 0) + { + seconds = new ASN1Integer(accuracySeconds); + } + + ASN1Integer millis = null; + if (accuracyMillis > 0) + { + millis = new ASN1Integer(accuracyMillis); + } + + ASN1Integer micros = null; + if (accuracyMicros > 0) + { + micros = new ASN1Integer(accuracyMicros); + } + + accuracy = new Accuracy(seconds, millis, micros); + } + + ASN1Boolean derOrdering = null; + if (ordering) + { + derOrdering = new ASN1Boolean(ordering); + } + + ASN1Integer nonce = null; + if (request.getNonce() != null) + { + nonce = new ASN1Integer(request.getNonce()); + } + + ASN1ObjectIdentifier tsaPolicy = tsaPolicyOID; + if (request.getReqPolicy() != null) + { + tsaPolicy = request.getReqPolicy(); + } + + TSTInfo tstInfo = new TSTInfo(tsaPolicy, + messageImprint, new ASN1Integer(serialNumber), + new ASN1GeneralizedTime(genTime), accuracy, derOrdering, + nonce, tsa, request.getExtensions()); + + try + { + CMSSignedDataGenerator signedDataGenerator = new CMSSignedDataGenerator(); + + if (request.getCertReq()) + { + // TODO: do we need to check certs non-empty? + signedDataGenerator.addCertificates(new CollectionStore(certs)); + signedDataGenerator.addCRLs(new CollectionStore(crls)); + signedDataGenerator.addAttributeCertificates(new CollectionStore(attrCerts)); + } + else + { + signedDataGenerator.addCRLs(new CollectionStore(crls)); + } + + signedDataGenerator.addSignerInfoGenerator(signerInfoGen); + + byte[] derEncodedTSTInfo = tstInfo.getEncoded(ASN1Encoding.DER); + + CMSSignedData signedData = signedDataGenerator.generate(new CMSProcessableByteArray(PKCSObjectIdentifiers.id_ct_TSTInfo, derEncodedTSTInfo), true); + + return new TimeStampToken(signedData); + } + catch (CMSException cmsEx) + { + throw new TSPException("Error generating time-stamp token", cmsEx); + } + catch (IOException e) + { + throw new TSPException("Exception encoding info", e); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TimeStampTokenInfo.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TimeStampTokenInfo.java new file mode 100644 index 000000000..e9f706507 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/TimeStampTokenInfo.java @@ -0,0 +1,121 @@ +package org.spongycastle.tsp; + +import java.io.IOException; +import java.math.BigInteger; +import java.text.ParseException; +import java.util.Date; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.tsp.Accuracy; +import org.spongycastle.asn1.tsp.TSTInfo; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.GeneralName; + +public class TimeStampTokenInfo +{ + TSTInfo tstInfo; + Date genTime; + + TimeStampTokenInfo(TSTInfo tstInfo) + throws TSPException, IOException + { + this.tstInfo = tstInfo; + + try + { + this.genTime = tstInfo.getGenTime().getDate(); + } + catch (ParseException e) + { + throw new TSPException("unable to parse genTime field"); + } + } + + public boolean isOrdered() + { + return tstInfo.getOrdering().isTrue(); + } + + public Accuracy getAccuracy() + { + return tstInfo.getAccuracy(); + } + + public Date getGenTime() + { + return genTime; + } + + public GenTimeAccuracy getGenTimeAccuracy() + { + if (this.getAccuracy() != null) + { + return new GenTimeAccuracy(this.getAccuracy()); + } + + return null; + } + + public ASN1ObjectIdentifier getPolicy() + { + return tstInfo.getPolicy(); + } + + public BigInteger getSerialNumber() + { + return tstInfo.getSerialNumber().getValue(); + } + + public GeneralName getTsa() + { + return tstInfo.getTsa(); + } + + /** + * @return the nonce value, null if there isn't one. + */ + public BigInteger getNonce() + { + if (tstInfo.getNonce() != null) + { + return tstInfo.getNonce().getValue(); + } + + return null; + } + + public AlgorithmIdentifier getHashAlgorithm() + { + return tstInfo.getMessageImprint().getHashAlgorithm(); + } + + public ASN1ObjectIdentifier getMessageImprintAlgOID() + { + return tstInfo.getMessageImprint().getHashAlgorithm().getAlgorithm(); + } + + public byte[] getMessageImprintDigest() + { + return tstInfo.getMessageImprint().getHashedMessage(); + } + + public byte[] getEncoded() + throws IOException + { + return tstInfo.getEncoded(); + } + + /** + * @deprecated use toASN1Structure + * @return + */ + public TSTInfo toTSTInfo() + { + return tstInfo; + } + + public TSTInfo toASN1Structure() + { + return tstInfo; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/cms/CMSTimeStampedData.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/cms/CMSTimeStampedData.java new file mode 100644 index 000000000..844f123a1 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/cms/CMSTimeStampedData.java @@ -0,0 +1,204 @@ +package org.spongycastle.tsp.cms; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; + +import org.spongycastle.asn1.ASN1InputStream; +import org.spongycastle.asn1.DERIA5String; +import org.spongycastle.asn1.cms.AttributeTable; +import org.spongycastle.asn1.cms.CMSObjectIdentifiers; +import org.spongycastle.asn1.cms.ContentInfo; +import org.spongycastle.asn1.cms.Evidence; +import org.spongycastle.asn1.cms.TimeStampAndCRL; +import org.spongycastle.asn1.cms.TimeStampTokenEvidence; +import org.spongycastle.asn1.cms.TimeStampedData; +import org.spongycastle.cms.CMSException; +import org.spongycastle.operator.DigestCalculator; +import org.spongycastle.operator.DigestCalculatorProvider; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.tsp.TimeStampToken; + +public class CMSTimeStampedData +{ + private TimeStampedData timeStampedData; + private ContentInfo contentInfo; + private TimeStampDataUtil util; + + public CMSTimeStampedData(ContentInfo contentInfo) + { + this.initialize(contentInfo); + } + + public CMSTimeStampedData(InputStream in) + throws IOException + { + try + { + initialize(ContentInfo.getInstance(new ASN1InputStream(in).readObject())); + } + catch (ClassCastException e) + { + throw new IOException("Malformed content: " + e); + } + catch (IllegalArgumentException e) + { + throw new IOException("Malformed content: " + e); + } + } + + public CMSTimeStampedData(byte[] baseData) + throws IOException + { + this(new ByteArrayInputStream(baseData)); + } + + private void initialize(ContentInfo contentInfo) + { + this.contentInfo = contentInfo; + + if (CMSObjectIdentifiers.timestampedData.equals(contentInfo.getContentType())) + { + this.timeStampedData = TimeStampedData.getInstance(contentInfo.getContent()); + } + else + { + throw new IllegalArgumentException("Malformed content - type must be " + CMSObjectIdentifiers.timestampedData.getId()); + } + + util = new TimeStampDataUtil(this.timeStampedData); + } + + public byte[] calculateNextHash(DigestCalculator calculator) + throws CMSException + { + return util.calculateNextHash(calculator); + } + + /** + * Return a new timeStampedData object with the additional token attached. + * + * @throws CMSException + */ + public CMSTimeStampedData addTimeStamp(TimeStampToken token) + throws CMSException + { + TimeStampAndCRL[] timeStamps = util.getTimeStamps(); + TimeStampAndCRL[] newTimeStamps = new TimeStampAndCRL[timeStamps.length + 1]; + + System.arraycopy(timeStamps, 0, newTimeStamps, 0, timeStamps.length); + + newTimeStamps[timeStamps.length] = new TimeStampAndCRL(token.toCMSSignedData().toASN1Structure()); + + return new CMSTimeStampedData(new ContentInfo(CMSObjectIdentifiers.timestampedData, new TimeStampedData(timeStampedData.getDataUri(), timeStampedData.getMetaData(), timeStampedData.getContent(), new Evidence(new TimeStampTokenEvidence(newTimeStamps))))); + } + + public byte[] getContent() + { + if (timeStampedData.getContent() != null) + { + return timeStampedData.getContent().getOctets(); + } + + return null; + } + + public URI getDataUri() + throws URISyntaxException + { + DERIA5String dataURI = this.timeStampedData.getDataUri(); + + if (dataURI != null) + { + return new URI(dataURI.getString()); + } + + return null; + } + + public String getFileName() + { + return util.getFileName(); + } + + public String getMediaType() + { + return util.getMediaType(); + } + + public AttributeTable getOtherMetaData() + { + return util.getOtherMetaData(); + } + + public TimeStampToken[] getTimeStampTokens() + throws CMSException + { + return util.getTimeStampTokens(); + } + + /** + * Initialise the passed in calculator with the MetaData for this message, if it is + * required as part of the initial message imprint calculation. + * + * @param calculator the digest calculator to be initialised. + * @throws CMSException if the MetaData is required and cannot be processed + */ + public void initialiseMessageImprintDigestCalculator(DigestCalculator calculator) + throws CMSException + { + util.initialiseMessageImprintDigestCalculator(calculator); + } + + /** + * Returns an appropriately initialised digest calculator based on the message imprint algorithm + * described in the first time stamp in the TemporalData for this message. If the metadata is required + * to be included in the digest calculation, the returned calculator will be pre-initialised. + * + * @param calculatorProvider a provider of DigestCalculator objects. + * @return an initialised digest calculator. + * @throws OperatorCreationException if the provider is unable to create the calculator. + */ + public DigestCalculator getMessageImprintDigestCalculator(DigestCalculatorProvider calculatorProvider) + throws OperatorCreationException + { + return util.getMessageImprintDigestCalculator(calculatorProvider); + } + + /** + * Validate the digests present in the TimeStampTokens contained in the CMSTimeStampedData. + * + * @param calculatorProvider provider for digest calculators + * @param dataDigest the calculated data digest for the message + * @throws ImprintDigestInvalidException if an imprint digest fails to compare + * @throws CMSException if an exception occurs processing the message. + */ + public void validate(DigestCalculatorProvider calculatorProvider, byte[] dataDigest) + throws ImprintDigestInvalidException, CMSException + { + util.validate(calculatorProvider, dataDigest); + } + + /** + * Validate the passed in timestamp token against the tokens and data present in the message. + * + * @param calculatorProvider provider for digest calculators + * @param dataDigest the calculated data digest for the message. + * @param timeStampToken the timestamp token of interest. + * @throws ImprintDigestInvalidException if the token is not present in the message, or an imprint digest fails to compare. + * @throws CMSException if an exception occurs processing the message. + */ + public void validate(DigestCalculatorProvider calculatorProvider, byte[] dataDigest, TimeStampToken timeStampToken) + throws ImprintDigestInvalidException, CMSException + { + util.validate(calculatorProvider, dataDigest, timeStampToken); + } + + public byte[] getEncoded() + throws IOException + { + return contentInfo.getEncoded(); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/cms/CMSTimeStampedDataGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/cms/CMSTimeStampedDataGenerator.java new file mode 100644 index 000000000..a1ad0c0ff --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/cms/CMSTimeStampedDataGenerator.java @@ -0,0 +1,70 @@ +package org.spongycastle.tsp.cms; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.BEROctetString; +import org.spongycastle.asn1.DERIA5String; +import org.spongycastle.asn1.cms.CMSObjectIdentifiers; +import org.spongycastle.asn1.cms.ContentInfo; +import org.spongycastle.asn1.cms.Evidence; +import org.spongycastle.asn1.cms.TimeStampAndCRL; +import org.spongycastle.asn1.cms.TimeStampTokenEvidence; +import org.spongycastle.asn1.cms.TimeStampedData; +import org.spongycastle.cms.CMSException; +import org.spongycastle.tsp.TimeStampToken; +import org.spongycastle.util.io.Streams; + +public class CMSTimeStampedDataGenerator + extends CMSTimeStampedGenerator +{ + public CMSTimeStampedData generate(TimeStampToken timeStamp) throws CMSException + { + return generate(timeStamp, (InputStream)null); + } + + public CMSTimeStampedData generate(TimeStampToken timeStamp, byte[] content) throws CMSException + { + return generate(timeStamp, new ByteArrayInputStream(content)); + } + + public CMSTimeStampedData generate(TimeStampToken timeStamp, InputStream content) + throws CMSException + { + ByteArrayOutputStream contentOut = new ByteArrayOutputStream(); + + if (content != null) + { + try + { + Streams.pipeAll(content, contentOut); + } + catch (IOException e) + { + throw new CMSException("exception encapsulating content: " + e.getMessage(), e); + } + } + + ASN1OctetString encContent = null; + + if (contentOut.size() != 0) + { + encContent = new BEROctetString(contentOut.toByteArray()); + } + + TimeStampAndCRL stamp = new TimeStampAndCRL(timeStamp.toCMSSignedData().toASN1Structure()); + + DERIA5String asn1DataUri = null; + + if (dataUri != null) + { + asn1DataUri = new DERIA5String(dataUri.toString()); + } + + return new CMSTimeStampedData(new ContentInfo(CMSObjectIdentifiers.timestampedData, new TimeStampedData(asn1DataUri, metaData, encContent, new Evidence(new TimeStampTokenEvidence(stamp))))); + } +} + diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/cms/CMSTimeStampedDataParser.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/cms/CMSTimeStampedDataParser.java new file mode 100644 index 000000000..c3518b778 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/cms/CMSTimeStampedDataParser.java @@ -0,0 +1,207 @@ +package org.spongycastle.tsp.cms; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; + +import org.spongycastle.asn1.BERTags; +import org.spongycastle.asn1.DERIA5String; +import org.spongycastle.asn1.cms.AttributeTable; +import org.spongycastle.asn1.cms.CMSObjectIdentifiers; +import org.spongycastle.asn1.cms.ContentInfoParser; +import org.spongycastle.asn1.cms.TimeStampedDataParser; +import org.spongycastle.cms.CMSContentInfoParser; +import org.spongycastle.cms.CMSException; +import org.spongycastle.operator.DigestCalculator; +import org.spongycastle.operator.DigestCalculatorProvider; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.tsp.TimeStampToken; +import org.spongycastle.util.io.Streams; + +public class CMSTimeStampedDataParser + extends CMSContentInfoParser +{ + private TimeStampedDataParser timeStampedData; + private TimeStampDataUtil util; + + public CMSTimeStampedDataParser(InputStream in) + throws CMSException + { + super(in); + + initialize(_contentInfo); + } + + public CMSTimeStampedDataParser(byte[] baseData) + throws CMSException + { + this(new ByteArrayInputStream(baseData)); + } + + private void initialize(ContentInfoParser contentInfo) + throws CMSException + { + try + { + if (CMSObjectIdentifiers.timestampedData.equals(contentInfo.getContentType())) + { + this.timeStampedData = TimeStampedDataParser.getInstance(contentInfo.getContent(BERTags.SEQUENCE)); + } + else + { + throw new IllegalArgumentException("Malformed content - type must be " + CMSObjectIdentifiers.timestampedData.getId()); + } + } + catch (IOException e) + { + throw new CMSException("parsing exception: " + e.getMessage(), e); + } + } + + public byte[] calculateNextHash(DigestCalculator calculator) + throws CMSException + { + return util.calculateNextHash(calculator); + } + + public InputStream getContent() + { + if (timeStampedData.getContent() != null) + { + return timeStampedData.getContent().getOctetStream(); + } + + return null; + } + + public URI getDataUri() + throws URISyntaxException + { + DERIA5String dataURI = this.timeStampedData.getDataUri(); + + if (dataURI != null) + { + return new URI(dataURI.getString()); + } + + return null; + } + + public String getFileName() + { + return util.getFileName(); + } + + public String getMediaType() + { + return util.getMediaType(); + } + + public AttributeTable getOtherMetaData() + { + return util.getOtherMetaData(); + } + + /** + * Initialise the passed in calculator with the MetaData for this message, if it is + * required as part of the initial message imprint calculation. + * + * @param calculator the digest calculator to be initialised. + * @throws CMSException if the MetaData is required and cannot be processed + */ + public void initialiseMessageImprintDigestCalculator(DigestCalculator calculator) + throws CMSException + { + util.initialiseMessageImprintDigestCalculator(calculator); + } + + /** + * Returns an appropriately initialised digest calculator based on the message imprint algorithm + * described in the first time stamp in the TemporalData for this message. If the metadata is required + * to be included in the digest calculation, the returned calculator will be pre-initialised. + * + * @param calculatorProvider a provider of DigestCalculator objects. + * @return an initialised digest calculator. + * @throws OperatorCreationException if the provider is unable to create the calculator. + */ + public DigestCalculator getMessageImprintDigestCalculator(DigestCalculatorProvider calculatorProvider) + throws OperatorCreationException + { + try + { + parseTimeStamps(); + } + catch (CMSException e) + { + throw new OperatorCreationException("unable to extract algorithm ID: " + e.getMessage(), e); + } + + return util.getMessageImprintDigestCalculator(calculatorProvider); + } + + public TimeStampToken[] getTimeStampTokens() + throws CMSException + { + parseTimeStamps(); + + return util.getTimeStampTokens(); + } + + /** + * Validate the digests present in the TimeStampTokens contained in the CMSTimeStampedData. + * + * @param calculatorProvider provider for digest calculators + * @param dataDigest the calculated data digest for the message + * @throws ImprintDigestInvalidException if an imprint digest fails to compare + * @throws CMSException if an exception occurs processing the message. + */ + public void validate(DigestCalculatorProvider calculatorProvider, byte[] dataDigest) + throws ImprintDigestInvalidException, CMSException + { + parseTimeStamps(); + + util.validate(calculatorProvider, dataDigest); + } + + /** + * Validate the passed in timestamp token against the tokens and data present in the message. + * + * @param calculatorProvider provider for digest calculators + * @param dataDigest the calculated data digest for the message. + * @param timeStampToken the timestamp token of interest. + * @throws ImprintDigestInvalidException if the token is not present in the message, or an imprint digest fails to compare. + * @throws CMSException if an exception occurs processing the message. + */ + public void validate(DigestCalculatorProvider calculatorProvider, byte[] dataDigest, TimeStampToken timeStampToken) + throws ImprintDigestInvalidException, CMSException + { + parseTimeStamps(); + + util.validate(calculatorProvider, dataDigest, timeStampToken); + } + + private void parseTimeStamps() + throws CMSException + { + try + { + if (util == null) + { + InputStream cont = this.getContent(); + + if (cont != null) + { + Streams.drain(cont); + } + + util = new TimeStampDataUtil(timeStampedData); + } + } + catch (IOException e) + { + throw new CMSException("unable to parse evidence block: " + e.getMessage(), e); + } + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/cms/CMSTimeStampedGenerator.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/cms/CMSTimeStampedGenerator.java new file mode 100644 index 000000000..9dbeb97fa --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/cms/CMSTimeStampedGenerator.java @@ -0,0 +1,88 @@ +package org.spongycastle.tsp.cms; + +import java.net.URI; + +import org.spongycastle.asn1.ASN1Boolean; +import org.spongycastle.asn1.DERIA5String; +import org.spongycastle.asn1.DERUTF8String; +import org.spongycastle.asn1.cms.Attributes; +import org.spongycastle.asn1.cms.MetaData; +import org.spongycastle.cms.CMSException; +import org.spongycastle.operator.DigestCalculator; + +public class CMSTimeStampedGenerator +{ + protected MetaData metaData; + protected URI dataUri; + + /** + * Set the dataURI to be included in message. + * + * @param dataUri URI for the data the initial message imprint digest is based on. + */ + public void setDataUri(URI dataUri) + { + this.dataUri = dataUri; + } + + /** + * Set the MetaData for the generated message. + * + * @param hashProtected true if the MetaData should be included in first imprint calculation, false otherwise. + * @param fileName optional file name, may be null. + * @param mediaType optional media type, may be null. + */ + public void setMetaData(boolean hashProtected, String fileName, String mediaType) + { + setMetaData(hashProtected, fileName, mediaType, null); + } + + /** + * Set the MetaData for the generated message. + * + * @param hashProtected true if the MetaData should be included in first imprint calculation, false otherwise. + * @param fileName optional file name, may be null. + * @param mediaType optional media type, may be null. + * @param attributes optional attributes, may be null. + */ + public void setMetaData(boolean hashProtected, String fileName, String mediaType, Attributes attributes) + { + DERUTF8String asn1FileName = null; + + if (fileName != null) + { + asn1FileName = new DERUTF8String(fileName); + } + + DERIA5String asn1MediaType = null; + + if (mediaType != null) + { + asn1MediaType = new DERIA5String(mediaType); + } + + setMetaData(hashProtected, asn1FileName, asn1MediaType, attributes); + } + + private void setMetaData(boolean hashProtected, DERUTF8String fileName, DERIA5String mediaType, Attributes attributes) + { + this.metaData = new MetaData(ASN1Boolean.getInstance(hashProtected), fileName, mediaType, attributes); + } + + /** + * Initialise the passed in calculator with the MetaData for this message, if it is + * required as part of the initial message imprint calculation. After initialisation the + * calculator can then be used to calculate the initial message imprint digest for the first + * timestamp. + * + * @param calculator the digest calculator to be initialised. + * @throws CMSException if the MetaData is required and cannot be processed + */ + public void initialiseMessageImprintDigestCalculator(DigestCalculator calculator) + throws CMSException + { + MetaDataUtil util = new MetaDataUtil(metaData); + + util.initialiseMessageImprintDigestCalculator(calculator); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/cms/ImprintDigestInvalidException.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/cms/ImprintDigestInvalidException.java new file mode 100644 index 000000000..1dfc2bb36 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/cms/ImprintDigestInvalidException.java @@ -0,0 +1,21 @@ +package org.spongycastle.tsp.cms; + +import org.spongycastle.tsp.TimeStampToken; + +public class ImprintDigestInvalidException + extends Exception +{ + private TimeStampToken token; + + public ImprintDigestInvalidException(String message, TimeStampToken token) + { + super(message); + + this.token = token; + } + + public TimeStampToken getTimeStampToken() + { + return token; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/cms/MetaDataUtil.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/cms/MetaDataUtil.java new file mode 100644 index 000000000..f4ad579ad --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/cms/MetaDataUtil.java @@ -0,0 +1,76 @@ +package org.spongycastle.tsp.cms; + +import java.io.IOException; + +import org.spongycastle.asn1.ASN1Encoding; +import org.spongycastle.asn1.ASN1String; +import org.spongycastle.asn1.cms.Attributes; +import org.spongycastle.asn1.cms.MetaData; +import org.spongycastle.cms.CMSException; +import org.spongycastle.operator.DigestCalculator; + +class MetaDataUtil +{ + private final MetaData metaData; + + MetaDataUtil(MetaData metaData) + { + this.metaData = metaData; + } + + void initialiseMessageImprintDigestCalculator(DigestCalculator calculator) + throws CMSException + { + if (metaData != null && metaData.isHashProtected()) + { + try + { + calculator.getOutputStream().write(metaData.getEncoded(ASN1Encoding.DER)); + } + catch (IOException e) + { + throw new CMSException("unable to initialise calculator from metaData: " + e.getMessage(), e); + } + } + } + + String getFileName() + { + if (metaData != null) + { + return convertString(metaData.getFileName()); + } + + return null; + } + + String getMediaType() + { + if (metaData != null) + { + return convertString(metaData.getMediaType()); + } + + return null; + } + + Attributes getOtherMetaData() + { + if (metaData != null) + { + return metaData.getOtherMetaData(); + } + + return null; + } + + private String convertString(ASN1String s) + { + if (s != null) + { + return s.toString(); + } + + return null; + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/cms/TimeStampDataUtil.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/cms/TimeStampDataUtil.java new file mode 100644 index 000000000..fd1bcc8dc --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/tsp/cms/TimeStampDataUtil.java @@ -0,0 +1,256 @@ +package org.spongycastle.tsp.cms; + +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.asn1.ASN1Encoding; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.cms.AttributeTable; +import org.spongycastle.asn1.cms.ContentInfo; +import org.spongycastle.asn1.cms.Evidence; +import org.spongycastle.asn1.cms.TimeStampAndCRL; +import org.spongycastle.asn1.cms.TimeStampedData; +import org.spongycastle.asn1.cms.TimeStampedDataParser; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.cms.CMSException; +import org.spongycastle.operator.DigestCalculator; +import org.spongycastle.operator.DigestCalculatorProvider; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.tsp.TSPException; +import org.spongycastle.tsp.TimeStampToken; +import org.spongycastle.tsp.TimeStampTokenInfo; +import org.spongycastle.util.Arrays; + +class TimeStampDataUtil +{ + private final TimeStampAndCRL[] timeStamps; + + private final MetaDataUtil metaDataUtil; + + TimeStampDataUtil(TimeStampedData timeStampedData) + { + this.metaDataUtil = new MetaDataUtil(timeStampedData.getMetaData()); + + Evidence evidence = timeStampedData.getTemporalEvidence(); + this.timeStamps = evidence.getTstEvidence().toTimeStampAndCRLArray(); + } + + TimeStampDataUtil(TimeStampedDataParser timeStampedData) + throws IOException + { + this.metaDataUtil = new MetaDataUtil(timeStampedData.getMetaData()); + + Evidence evidence = timeStampedData.getTemporalEvidence(); + this.timeStamps = evidence.getTstEvidence().toTimeStampAndCRLArray(); + } + + TimeStampToken getTimeStampToken(TimeStampAndCRL timeStampAndCRL) + throws CMSException + { + ContentInfo timeStampToken = timeStampAndCRL.getTimeStampToken(); + + try + { + TimeStampToken token = new TimeStampToken(timeStampToken); + return token; + } + catch (IOException e) + { + throw new CMSException("unable to parse token data: " + e.getMessage(), e); + } + catch (TSPException e) + { + if (e.getCause() instanceof CMSException) + { + throw (CMSException)e.getCause(); + } + + throw new CMSException("token data invalid: " + e.getMessage(), e); + } + catch (IllegalArgumentException e) + { + throw new CMSException("token data invalid: " + e.getMessage(), e); + } + } + + void initialiseMessageImprintDigestCalculator(DigestCalculator calculator) + throws CMSException + { + metaDataUtil.initialiseMessageImprintDigestCalculator(calculator); + } + + DigestCalculator getMessageImprintDigestCalculator(DigestCalculatorProvider calculatorProvider) + throws OperatorCreationException + { + TimeStampToken token; + + try + { + token = this.getTimeStampToken(timeStamps[0]); + + TimeStampTokenInfo info = token.getTimeStampInfo(); + ASN1ObjectIdentifier algOID = info.getMessageImprintAlgOID(); + + DigestCalculator calc = calculatorProvider.get(new AlgorithmIdentifier(algOID)); + + initialiseMessageImprintDigestCalculator(calc); + + return calc; + } + catch (CMSException e) + { + throw new OperatorCreationException("unable to extract algorithm ID: " + e.getMessage(), e); + } + } + + TimeStampToken[] getTimeStampTokens() + throws CMSException + { + TimeStampToken[] tokens = new TimeStampToken[timeStamps.length]; + for (int i = 0; i < timeStamps.length; i++) + { + tokens[i] = this.getTimeStampToken(timeStamps[i]); + } + + return tokens; + } + + TimeStampAndCRL[] getTimeStamps() + { + return timeStamps; + } + + byte[] calculateNextHash(DigestCalculator calculator) + throws CMSException + { + TimeStampAndCRL tspToken = timeStamps[timeStamps.length - 1]; + + OutputStream out = calculator.getOutputStream(); + + try + { + out.write(tspToken.getEncoded(ASN1Encoding.DER)); + + out.close(); + + return calculator.getDigest(); + } + catch (IOException e) + { + throw new CMSException("exception calculating hash: " + e.getMessage(), e); + } + } + + /** + * Validate the digests present in the TimeStampTokens contained in the CMSTimeStampedData. + */ + void validate(DigestCalculatorProvider calculatorProvider, byte[] dataDigest) + throws ImprintDigestInvalidException, CMSException + { + byte[] currentDigest = dataDigest; + + for (int i = 0; i < timeStamps.length; i++) + { + try + { + TimeStampToken token = this.getTimeStampToken(timeStamps[i]); + if (i > 0) + { + TimeStampTokenInfo info = token.getTimeStampInfo(); + DigestCalculator calculator = calculatorProvider.get(info.getHashAlgorithm()); + + calculator.getOutputStream().write(timeStamps[i - 1].getEncoded(ASN1Encoding.DER)); + + currentDigest = calculator.getDigest(); + } + + this.compareDigest(token, currentDigest); + } + catch (IOException e) + { + throw new CMSException("exception calculating hash: " + e.getMessage(), e); + } + catch (OperatorCreationException e) + { + throw new CMSException("cannot create digest: " + e.getMessage(), e); + } + } + } + + void validate(DigestCalculatorProvider calculatorProvider, byte[] dataDigest, TimeStampToken timeStampToken) + throws ImprintDigestInvalidException, CMSException + { + byte[] currentDigest = dataDigest; + byte[] encToken; + + try + { + encToken = timeStampToken.getEncoded(); + } + catch (IOException e) + { + throw new CMSException("exception encoding timeStampToken: " + e.getMessage(), e); + } + + for (int i = 0; i < timeStamps.length; i++) + { + try + { + TimeStampToken token = this.getTimeStampToken(timeStamps[i]); + if (i > 0) + { + TimeStampTokenInfo info = token.getTimeStampInfo(); + DigestCalculator calculator = calculatorProvider.get(info.getHashAlgorithm()); + + calculator.getOutputStream().write(timeStamps[i - 1].getEncoded(ASN1Encoding.DER)); + + currentDigest = calculator.getDigest(); + } + + this.compareDigest(token, currentDigest); + + if (Arrays.areEqual(token.getEncoded(), encToken)) + { + return; + } + } + catch (IOException e) + { + throw new CMSException("exception calculating hash: " + e.getMessage(), e); + } + catch (OperatorCreationException e) + { + throw new CMSException("cannot create digest: " + e.getMessage(), e); + } + } + + throw new ImprintDigestInvalidException("passed in token not associated with timestamps present", timeStampToken); + } + + private void compareDigest(TimeStampToken timeStampToken, byte[] digest) + throws ImprintDigestInvalidException + { + TimeStampTokenInfo info = timeStampToken.getTimeStampInfo(); + byte[] tsrMessageDigest = info.getMessageImprintDigest(); + + if (!Arrays.areEqual(digest, tsrMessageDigest)) + { + throw new ImprintDigestInvalidException("hash calculated is different from MessageImprintDigest found in TimeStampToken", timeStampToken); + } + } + + String getFileName() + { + return metaDataUtil.getFileName(); + } + + String getMediaType() + { + return metaDataUtil.getMediaType(); + } + + AttributeTable getOtherMetaData() + { + return new AttributeTable(metaDataUtil.getOtherMetaData()); + } +} diff --git a/libraries/spongycastle/pkix/src/main/java/org/spongycastle/voms/VOMSAttribute.java b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/voms/VOMSAttribute.java new file mode 100644 index 000000000..4e7aac737 --- /dev/null +++ b/libraries/spongycastle/pkix/src/main/java/org/spongycastle/voms/VOMSAttribute.java @@ -0,0 +1,242 @@ +package org.spongycastle.voms; + +import java.util.List; +import java.util.ArrayList; + +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.DERIA5String; +import org.spongycastle.asn1.x509.IetfAttrSyntax; +import org.spongycastle.x509.X509Attribute; +import org.spongycastle.x509.X509AttributeCertificate; + + +/** + * Representation of the authorization information (VO, server address + * and list of Fully Qualified Attribute Names, or FQANs) contained in + * a VOMS attribute certificate. + */ +public class VOMSAttribute +{ + + /** + * The ASN.1 object identifier for VOMS attributes + */ + public static final String VOMS_ATTR_OID = "1.3.6.1.4.1.8005.100.100.4"; + private X509AttributeCertificate myAC; + private String myHostPort; + private String myVo; + private List myStringList = new ArrayList(); + private List myFQANs = new ArrayList(); + + /** + * Parses the contents of an attribute certificate.<br> + * <b>NOTE:</b> Cryptographic signatures, time stamps etc. will <b>not</b> be checked. + * + * @param ac the attribute certificate to parse for VOMS attributes + */ + public VOMSAttribute(X509AttributeCertificate ac) + { + if (ac == null) + { + throw new IllegalArgumentException("VOMSAttribute: AttributeCertificate is NULL"); + } + + myAC = ac; + + X509Attribute[] l = ac.getAttributes(VOMS_ATTR_OID); + + if (l == null) + { + return; + } + + try + { + for (int i = 0; i != l.length; i++) + { + IetfAttrSyntax attr = IetfAttrSyntax.getInstance(l[i].getValues()[0]); + + // policyAuthority is on the format <vo>/<host>:<port> + String url = ((DERIA5String)attr.getPolicyAuthority().getNames()[0].getName()).getString(); + int idx = url.indexOf("://"); + + if ((idx < 0) || (idx == (url.length() - 1))) + { + throw new IllegalArgumentException("Bad encoding of VOMS policyAuthority : [" + url + "]"); + } + + myVo = url.substring(0, idx); + myHostPort = url.substring(idx + 3); + + if (attr.getValueType() != IetfAttrSyntax.VALUE_OCTETS) + { + throw new IllegalArgumentException( + "VOMS attribute values are not encoded as octet strings, policyAuthority = " + url); + } + + ASN1OctetString[] values = (ASN1OctetString[])attr.getValues(); + for (int j = 0; j != values.length; j++) + { + String fqan = new String(values[j].getOctets()); + FQAN f = new FQAN(fqan); + + if (!myStringList.contains(fqan) && fqan.startsWith("/" + myVo + "/")) + { + myStringList.add(fqan); + myFQANs.add(f); + } + } + } + } + catch (IllegalArgumentException ie) + { + throw ie; + } + catch (Exception e) + { + throw new IllegalArgumentException("Badly encoded VOMS extension in AC issued by " + + ac.getIssuer()); + } + } + + /** + * @return The AttributeCertificate containing the VOMS information + */ + public X509AttributeCertificate getAC() + { + return myAC; + } + + /** + * @return List of String of the VOMS fully qualified + * attributes names (FQANs):<br> + * <code>/vo[/group[/group2...]][/Role=[role]][/Capability=capability]</code> + */ + public List getFullyQualifiedAttributes() + { + return myStringList; + } + + /** + * @return List of FQAN of the VOMS fully qualified + * attributes names (FQANs) + */ + public List getListOfFQAN() + { + return myFQANs; + } + + /** + * Returns the address of the issuing VOMS server, on the form <code><host>:<port></code> + * @return String + */ + public String getHostPort() + { + return myHostPort; + } + + /** + * Returns the VO name + * @return + */ + public String getVO() + { + return myVo; + } + + public String toString() + { + return "VO :" + myVo + "\n" + "HostPort:" + myHostPort + "\n" + "FQANs :" + myFQANs; + } + + /** + * Inner class providing a container of the group,role,capability + * information triplet in an FQAN. + */ + public class FQAN + { + String fqan; + String group; + String role; + String capability; + + public FQAN(String fqan) + { + this.fqan = fqan; + } + + public FQAN(String group, String role, String capability) + { + this.group = group; + this.role = role; + this.capability = capability; + } + + public String getFQAN() + { + if (fqan != null) + { + return fqan; + } + + fqan = group + "/Role=" + ((role != null) ? role : "") + + ((capability != null) ? ("/Capability=" + capability) : ""); + + return fqan; + } + + protected void split() + { + int len = fqan.length(); + int i = fqan.indexOf("/Role="); + + if (i < 0) + { + return; + } + + group = fqan.substring(0, i); + + int j = fqan.indexOf("/Capability=", i + 6); + String s = (j < 0) ? fqan.substring(i + 6) : fqan.substring(i + 6, j); + role = (s.length() == 0) ? null : s; + s = (j < 0) ? null : fqan.substring(j + 12); + capability = ((s == null) || (s.length() == 0)) ? null : s; + } + + public String getGroup() + { + if ((group == null) && (fqan != null)) + { + split(); + } + + return group; + } + + public String getRole() + { + if ((group == null) && (fqan != null)) + { + split(); + } + + return role; + } + + public String getCapability() + { + if ((group == null) && (fqan != null)) + { + split(); + } + + return capability; + } + + public String toString() + { + return getFQAN(); + } + } +} |