From 7cb752aaf746dc0b473afeb9e892b7fbc12666c5 Mon Sep 17 00:00:00 2001 From: Roberto Tyley Date: Mon, 14 Jul 2014 22:38:01 +0100 Subject: Execute become-spongy.sh https://github.com/rtyley/spongycastle/blob/3040af/become-spongy.sh --- .../cert/AttributeCertificateHolder.java | 357 ++++++++++++++++ .../cert/AttributeCertificateIssuer.java | 147 +++++++ .../java/org/spongycastle/cert/CertException.java | 27 ++ .../org/spongycastle/cert/CertIOException.java | 29 ++ .../spongycastle/cert/CertRuntimeException.java | 19 + .../main/java/org/spongycastle/cert/CertUtils.java | 244 +++++++++++ .../cert/X509AttributeCertificateHolder.java | 366 +++++++++++++++++ .../org/spongycastle/cert/X509CRLEntryHolder.java | 144 +++++++ .../java/org/spongycastle/cert/X509CRLHolder.java | 317 +++++++++++++++ .../spongycastle/cert/X509CertificateHolder.java | 327 +++++++++++++++ .../cert/X509ContentVerifierProviderBuilder.java | 14 + .../org/spongycastle/cert/X509ExtensionUtils.java | 132 ++++++ .../cert/X509v1CertificateBuilder.java | 101 +++++ .../cert/X509v2AttributeCertificateBuilder.java | 162 ++++++++ .../org/spongycastle/cert/X509v2CRLBuilder.java | 266 ++++++++++++ .../cert/X509v3CertificateBuilder.java | 195 +++++++++ .../spongycastle/cert/bc/BcX509ExtensionUtils.java | 91 +++++ .../cert/bc/BcX509v1CertificateBuilder.java | 33 ++ .../cert/bc/BcX509v3CertificateBuilder.java | 51 +++ .../org/spongycastle/cert/cmp/CMPException.java | 24 ++ .../spongycastle/cert/cmp/CMPRuntimeException.java | 19 + .../java/org/spongycastle/cert/cmp/CMPUtil.java | 26 ++ .../cert/cmp/CertificateConfirmationContent.java | 41 ++ .../cmp/CertificateConfirmationContentBuilder.java | 78 ++++ .../spongycastle/cert/cmp/CertificateStatus.java | 60 +++ .../spongycastle/cert/cmp/GeneralPKIMessage.java | 82 ++++ .../spongycastle/cert/cmp/ProtectedPKIMessage.java | 198 +++++++++ .../cert/cmp/ProtectedPKIMessageBuilder.java | 306 ++++++++++++++ .../spongycastle/cert/cmp/RevocationDetails.java | 36 ++ .../cert/cmp/RevocationDetailsBuilder.java | 59 +++ .../cert/crmf/AuthenticatorControl.java | 57 +++ .../org/spongycastle/cert/crmf/CRMFException.java | 19 + .../cert/crmf/CRMFRuntimeException.java | 19 + .../java/org/spongycastle/cert/crmf/CRMFUtil.java | 42 ++ .../cert/crmf/CertificateRequestMessage.java | 309 ++++++++++++++ .../crmf/CertificateRequestMessageBuilder.java | 279 +++++++++++++ .../java/org/spongycastle/cert/crmf/Control.java | 24 ++ .../cert/crmf/EncryptedValueBuilder.java | 133 ++++++ .../cert/crmf/EncryptedValuePadder.java | 24 ++ .../cert/crmf/EncryptedValueParser.java | 103 +++++ .../spongycastle/cert/crmf/PKIArchiveControl.java | 104 +++++ .../cert/crmf/PKIArchiveControlBuilder.java | 78 ++++ .../org/spongycastle/cert/crmf/PKMACBuilder.java | 199 +++++++++ .../cert/crmf/PKMACValueGenerator.java | 41 ++ .../spongycastle/cert/crmf/PKMACValueVerifier.java | 43 ++ .../cert/crmf/PKMACValuesCalculator.java | 15 + .../crmf/ProofOfPossessionSigningKeyBuilder.java | 75 ++++ .../spongycastle/cert/crmf/RegTokenControl.java | 57 +++ .../cert/crmf/ValueDecryptorGenerator.java | 10 + .../cert/crmf/bc/BcFixedLengthMGF1Padder.java | 121 ++++++ .../spongycastle/cert/crmf/jcajce/CRMFHelper.java | 450 +++++++++++++++++++++ .../crmf/jcajce/JcaCertificateRequestMessage.java | 84 ++++ .../JcaCertificateRequestMessageBuilder.java | 57 +++ .../cert/crmf/jcajce/JcaEncryptedValueBuilder.java | 26 ++ .../crmf/jcajce/JcaPKIArchiveControlBuilder.java | 29 ++ .../JceAsymmetricValueDecryptorGenerator.java | 120 ++++++ .../cert/crmf/jcajce/JceCRMFEncryptorBuilder.java | 136 +++++++ .../cert/crmf/jcajce/JcePKMACValuesCalculator.java | 69 ++++ .../org/spongycastle/cert/jcajce/CertHelper.java | 17 + .../cert/jcajce/DefaultCertHelper.java | 14 + .../spongycastle/cert/jcajce/JcaAttrCertStore.java | 62 +++ .../cert/jcajce/JcaAttributeCertificateIssuer.java | 32 ++ .../org/spongycastle/cert/jcajce/JcaCRLStore.java | 63 +++ .../org/spongycastle/cert/jcajce/JcaCertStore.java | 64 +++ .../cert/jcajce/JcaCertStoreBuilder.java | 148 +++++++ .../spongycastle/cert/jcajce/JcaX500NameUtil.java | 29 ++ .../jcajce/JcaX509AttributeCertificateHolder.java | 26 ++ .../cert/jcajce/JcaX509CRLConverter.java | 103 +++++ .../spongycastle/cert/jcajce/JcaX509CRLHolder.java | 26 ++ .../cert/jcajce/JcaX509CertificateConverter.java | 116 ++++++ .../cert/jcajce/JcaX509CertificateHolder.java | 26 ++ .../JcaX509ContentVerifierProviderBuilder.java | 50 +++ .../cert/jcajce/JcaX509ExtensionUtils.java | 145 +++++++ .../cert/jcajce/JcaX509v1CertificateBuilder.java | 48 +++ .../cert/jcajce/JcaX509v2CRLBuilder.java | 23 ++ .../cert/jcajce/JcaX509v3CertificateBuilder.java | 119 ++++++ .../spongycastle/cert/jcajce/NamedCertHelper.java | 22 + .../cert/jcajce/ProviderCertHelper.java | 22 + .../org/spongycastle/cert/ocsp/BasicOCSPResp.java | 212 ++++++++++ .../cert/ocsp/BasicOCSPRespBuilder.java | 264 ++++++++++++ .../org/spongycastle/cert/ocsp/CertificateID.java | 156 +++++++ .../spongycastle/cert/ocsp/CertificateStatus.java | 6 + .../org/spongycastle/cert/ocsp/OCSPException.java | 27 ++ .../java/org/spongycastle/cert/ocsp/OCSPReq.java | 259 ++++++++++++ .../org/spongycastle/cert/ocsp/OCSPReqBuilder.java | 199 +++++++++ .../java/org/spongycastle/cert/ocsp/OCSPResp.java | 141 +++++++ .../spongycastle/cert/ocsp/OCSPRespBuilder.java | 59 +++ .../java/org/spongycastle/cert/ocsp/OCSPUtils.java | 64 +++ .../main/java/org/spongycastle/cert/ocsp/Req.java | 25 ++ .../java/org/spongycastle/cert/ocsp/RespData.java | 52 +++ .../java/org/spongycastle/cert/ocsp/RespID.java | 89 ++++ .../org/spongycastle/cert/ocsp/RevokedStatus.java | 55 +++ .../org/spongycastle/cert/ocsp/SingleResp.java | 102 +++++ .../org/spongycastle/cert/ocsp/UnknownStatus.java | 12 + .../cert/ocsp/jcajce/JcaBasicOCSPRespBuilder.java | 18 + .../cert/ocsp/jcajce/JcaCertificateID.java | 20 + .../spongycastle/cert/ocsp/jcajce/JcaRespID.java | 26 ++ .../java/org/spongycastle/cert/path/CertPath.java | 80 ++++ .../org/spongycastle/cert/path/CertPathUtils.java | 21 + .../spongycastle/cert/path/CertPathValidation.java | 11 + .../cert/path/CertPathValidationContext.java | 61 +++ .../cert/path/CertPathValidationException.java | 24 ++ .../cert/path/CertPathValidationResult.java | 66 +++ .../cert/path/CertPathValidationResultBuilder.java | 14 + .../validations/BasicConstraintsValidation.java | 103 +++++ .../cert/path/validations/CRLValidation.java | 78 ++++ .../validations/CertificatePoliciesValidation.java | 146 +++++++ .../CertificatePoliciesValidationBuilder.java | 35 ++ .../cert/path/validations/KeyUsageValidation.java | 63 +++ .../validations/ParentCertIssuedValidation.java | 127 ++++++ .../cert/path/validations/ValidationUtils.java | 11 + .../cert/selector/MSOutlookKeyIdCalculator.java | 422 +++++++++++++++++++ .../X509AttributeCertificateHolderSelector.java | 268 ++++++++++++ ...9AttributeCertificateHolderSelectorBuilder.java | 194 +++++++++ .../selector/X509CertificateHolderSelector.java | 152 +++++++ .../cert/selector/jcajce/JcaSelectorConverter.java | 35 ++ .../jcajce/JcaX509CertSelectorConverter.java | 57 +++ .../jcajce/JcaX509CertificateHolderSelector.java | 72 ++++ 118 files changed, 11655 insertions(+) create mode 100644 pkix/src/main/java/org/spongycastle/cert/AttributeCertificateHolder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/AttributeCertificateIssuer.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/CertException.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/CertIOException.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/CertRuntimeException.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/CertUtils.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/X509AttributeCertificateHolder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/X509CRLEntryHolder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/X509CRLHolder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/X509CertificateHolder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/X509ContentVerifierProviderBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/X509ExtensionUtils.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/X509v1CertificateBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/X509v2AttributeCertificateBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/X509v2CRLBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/X509v3CertificateBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/bc/BcX509ExtensionUtils.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/bc/BcX509v1CertificateBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/bc/BcX509v3CertificateBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/cmp/CMPException.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/cmp/CMPRuntimeException.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/cmp/CMPUtil.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/cmp/CertificateConfirmationContent.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/cmp/CertificateConfirmationContentBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/cmp/CertificateStatus.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/cmp/GeneralPKIMessage.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/cmp/ProtectedPKIMessage.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/cmp/ProtectedPKIMessageBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/cmp/RevocationDetails.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/cmp/RevocationDetailsBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/crmf/AuthenticatorControl.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/crmf/CRMFException.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/crmf/CRMFRuntimeException.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/crmf/CRMFUtil.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/crmf/CertificateRequestMessage.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/crmf/CertificateRequestMessageBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/crmf/Control.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/crmf/EncryptedValueBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/crmf/EncryptedValuePadder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/crmf/EncryptedValueParser.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/crmf/PKIArchiveControl.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/crmf/PKIArchiveControlBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/crmf/PKMACBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/crmf/PKMACValueGenerator.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/crmf/PKMACValueVerifier.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/crmf/PKMACValuesCalculator.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/crmf/ProofOfPossessionSigningKeyBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/crmf/RegTokenControl.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/crmf/ValueDecryptorGenerator.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/crmf/bc/BcFixedLengthMGF1Padder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/CRMFHelper.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JcaCertificateRequestMessage.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JcaCertificateRequestMessageBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JcaEncryptedValueBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JcaPKIArchiveControlBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JceAsymmetricValueDecryptorGenerator.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JceCRMFEncryptorBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JcePKMACValuesCalculator.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/jcajce/CertHelper.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/jcajce/DefaultCertHelper.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/jcajce/JcaAttrCertStore.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/jcajce/JcaAttributeCertificateIssuer.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/jcajce/JcaCRLStore.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/jcajce/JcaCertStore.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/jcajce/JcaCertStoreBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX500NameUtil.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509AttributeCertificateHolder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509CRLConverter.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509CRLHolder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509CertificateConverter.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509CertificateHolder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509ContentVerifierProviderBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509ExtensionUtils.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509v1CertificateBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509v2CRLBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509v3CertificateBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/jcajce/NamedCertHelper.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/jcajce/ProviderCertHelper.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/ocsp/BasicOCSPResp.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/ocsp/BasicOCSPRespBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/ocsp/CertificateID.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/ocsp/CertificateStatus.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPException.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPReq.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPReqBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPResp.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPRespBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPUtils.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/ocsp/Req.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/ocsp/RespData.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/ocsp/RespID.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/ocsp/RevokedStatus.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/ocsp/SingleResp.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/ocsp/UnknownStatus.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/ocsp/jcajce/JcaBasicOCSPRespBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/ocsp/jcajce/JcaCertificateID.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/ocsp/jcajce/JcaRespID.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/path/CertPath.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/path/CertPathUtils.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/path/CertPathValidation.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/path/CertPathValidationContext.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/path/CertPathValidationException.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/path/CertPathValidationResult.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/path/CertPathValidationResultBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/path/validations/BasicConstraintsValidation.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/path/validations/CRLValidation.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/path/validations/CertificatePoliciesValidation.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/path/validations/CertificatePoliciesValidationBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/path/validations/KeyUsageValidation.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/path/validations/ParentCertIssuedValidation.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/path/validations/ValidationUtils.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/selector/MSOutlookKeyIdCalculator.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/selector/X509AttributeCertificateHolderSelector.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/selector/X509AttributeCertificateHolderSelectorBuilder.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/selector/X509CertificateHolderSelector.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/selector/jcajce/JcaSelectorConverter.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/selector/jcajce/JcaX509CertSelectorConverter.java create mode 100644 pkix/src/main/java/org/spongycastle/cert/selector/jcajce/JcaX509CertificateHolderSelector.java (limited to 'pkix/src/main/java/org/spongycastle/cert') diff --git a/pkix/src/main/java/org/spongycastle/cert/AttributeCertificateHolder.java b/pkix/src/main/java/org/spongycastle/cert/AttributeCertificateHolder.java new file mode 100644 index 00000000..610cdbe7 --- /dev/null +++ b/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. + * + *
+ *          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
+ *          }
+ * 
+ *

+ * Note: If objectDigestInfo comparisons are to be carried out the static + * method setDigestCalculatorProvider must be called once to configure the class + * to do the necessary calculations. + *

+ */ +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. + *

+ * digestedObjectType can be one of the following: + *

+ *

+ * 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 + * digestedObjectType is + * otherObjectDigest. + * @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. + *

+ *

+ * + * @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 null 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 null 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 null 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/pkix/src/main/java/org/spongycastle/cert/AttributeCertificateIssuer.java b/pkix/src/main/java/org/spongycastle/cert/AttributeCertificateIssuer.java new file mode 100644 index 00000000..e659e592 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/CertException.java b/pkix/src/main/java/org/spongycastle/cert/CertException.java new file mode 100644 index 00000000..b394ec89 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/CertIOException.java b/pkix/src/main/java/org/spongycastle/cert/CertIOException.java new file mode 100644 index 00000000..f21641d6 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/CertRuntimeException.java b/pkix/src/main/java/org/spongycastle/cert/CertRuntimeException.java new file mode 100644 index 00000000..a797083e --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/CertUtils.java b/pkix/src/main/java/org/spongycastle/cert/CertUtils.java new file mode 100644 index 00000000..d03f7843 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/X509AttributeCertificateHolder.java b/pkix/src/main/java/org/spongycastle/cert/X509AttributeCertificateHolder.java new file mode 100644 index 00000000..76c0cdf9 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/X509CRLEntryHolder.java b/pkix/src/main/java/org/spongycastle/cert/X509CRLEntryHolder.java new file mode 100644 index 00000000..da542c0e --- /dev/null +++ b/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. + *

+ * 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. + *

+ * + * @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/pkix/src/main/java/org/spongycastle/cert/X509CRLHolder.java b/pkix/src/main/java/org/spongycastle/cert/X509CRLHolder.java new file mode 100644 index 00000000..e94c2c1b --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/X509CertificateHolder.java b/pkix/src/main/java/org/spongycastle/cert/X509CertificateHolder.java new file mode 100644 index 00000000..fd53b92c --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/X509ContentVerifierProviderBuilder.java b/pkix/src/main/java/org/spongycastle/cert/X509ContentVerifierProviderBuilder.java new file mode 100644 index 00000000..d8429912 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/X509ExtensionUtils.java b/pkix/src/main/java/org/spongycastle/cert/X509ExtensionUtils.java new file mode 100644 index 00000000..96e603f8 --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/cert/X509ExtensionUtils.java @@ -0,0 +1,132 @@ +package org.spongycastle.cert; + +import java.io.IOException; +import java.io.OutputStream; +import java.math.BigInteger; + +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. + *

+ * Note: This class is not thread safe! + *

+ */ +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)); + } + + public AuthorityKeyIdentifier createAuthorityKeyIdentifier(SubjectPublicKeyInfo publicKeyInfo, GeneralNames generalNames, BigInteger serial) + { + return new AuthorityKeyIdentifier(calculateIdentifier(publicKeyInfo), generalNames, serial); + } + + /** + * Return a RFC 3280 type 1 key identifier. As in: + *
+     * (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).
+     * 
+ * @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: + *
+     * (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.
+     * 
+ * @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/pkix/src/main/java/org/spongycastle/cert/X509v1CertificateBuilder.java b/pkix/src/main/java/org/spongycastle/cert/X509v1CertificateBuilder.java new file mode 100644 index 00000000..083e1505 --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/cert/X509v1CertificateBuilder.java @@ -0,0 +1,101 @@ +package org.spongycastle.cert; + +import java.math.BigInteger; +import java.util.Date; +import java.util.Locale; + +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x509.ExtensionsGenerator; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.asn1.x509.Time; +import org.spongycastle.asn1.x509.V1TBSCertificateGenerator; +import org.spongycastle.asn1.x509.V3TBSCertificateGenerator; +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) + { + this(issuer, serial, new Time(notBefore), new Time(notAfter), subject, publicKeyInfo); + } + + /** + * Create a builder for a version 1 certificate. You may need to use this constructor if the default locale + * doesn't use a Gregorian calender so that the Time produced is compatible with other ASN.1 implementations. + * + * @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 dateLocale locale to be used for date interpretation. + * @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, Locale dateLocale, X500Name subject, SubjectPublicKeyInfo publicKeyInfo) + { + this(issuer, serial, new Time(notBefore, dateLocale), new Time(notAfter, dateLocale), subject, publicKeyInfo); + } + + /** + * Create a builder for a version 1 certificate. + * + * @param issuer the certificate issuer + * @param serial the certificate serial number + * @param notBefore the Time before which the certificate is not valid + * @param notAfter the Time 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, Time notBefore, Time 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(notBefore); + tbsGen.setEndDate(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/pkix/src/main/java/org/spongycastle/cert/X509v2AttributeCertificateBuilder.java b/pkix/src/main/java/org/spongycastle/cert/X509v2AttributeCertificateBuilder.java new file mode 100644 index 00000000..ffdd1567 --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/cert/X509v2AttributeCertificateBuilder.java @@ -0,0 +1,162 @@ +package org.spongycastle.cert; + +import java.math.BigInteger; +import java.util.Date; +import java.util.Locale; + +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; + + /** + * Base constructor. + * + * @param holder holder certificate details + * @param issuer issuer of this attribute certificate. + * @param serialNumber serial number of this attribute certificate. + * @param notBefore the date before which the certificate is not valid. + * @param notAfter the date after which the certificate is not valid. + */ + 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)); + } + + /** + * Base constructor with locale for interpreting dates. You may need to use this constructor if the default locale + * doesn't use a Gregorian calender so that the GeneralizedTime produced is compatible with other ASN.1 implementations. + * + * @param holder holder certificate details + * @param issuer issuer of this attribute certificate. + * @param serialNumber serial number of this attribute certificate. + * @param notBefore the date before which the certificate is not valid. + * @param notAfter the date after which the certificate is not valid. + * @param dateLocale locale to be used for date interpretation. + */ + public X509v2AttributeCertificateBuilder(AttributeCertificateHolder holder, AttributeCertificateIssuer issuer, BigInteger serialNumber, Date notBefore, Date notAfter, Locale dateLocale) + { + 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, dateLocale)); + acInfoGen.setEndDate(new ASN1GeneralizedTime(notAfter, dateLocale)); + } + + /** + * 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; + } + + /** + * Add a given extension field for the standard extensions tag (tag 3) using a byte encoding of the + * extension value. + * + * @param oid the OID defining the extension type. + * @param isCritical true if the extension is critical, false otherwise. + * @param encodedValue a byte array representing the encoding of the extension value. + * @return this builder object. + */ + public X509v2AttributeCertificateBuilder addExtension( + ASN1ObjectIdentifier oid, + boolean isCritical, + byte[] encodedValue) + throws CertIOException + { + extGenerator.addExtension(oid, isCritical, encodedValue); + + 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/pkix/src/main/java/org/spongycastle/cert/X509v2CRLBuilder.java b/pkix/src/main/java/org/spongycastle/cert/X509v2CRLBuilder.java new file mode 100644 index 00000000..69cb2406 --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/cert/X509v2CRLBuilder.java @@ -0,0 +1,266 @@ +package org.spongycastle.cert; + +import java.math.BigInteger; +import java.util.Date; +import java.util.Enumeration; +import java.util.Locale; + +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)); + } + + /** + * Basic constructor with Locale. You may need to use this constructor if the default locale + * doesn't use a Gregorian calender so that the Time produced is compatible with other ASN.1 implementations. + * + * @param issuer the issuer this CRL is associated with. + * @param thisUpdate the date of this update. + * @param dateLocale locale to be used for date interpretation. + */ + public X509v2CRLBuilder( + X500Name issuer, + Date thisUpdate, + Locale dateLocale) + { + tbsGen = new V2TBSCertListGenerator(); + extGenerator = new ExtensionsGenerator(); + + tbsGen.setIssuer(issuer); + tbsGen.setThisUpdate(new Time(thisUpdate, dateLocale)); + } + + /** + * Basic constructor. + * + * @param issuer the issuer this CRL is associated with. + * @param thisUpdate the Time of this update. + */ + public X509v2CRLBuilder( + X500Name issuer, + Time thisUpdate) + { + tbsGen = new V2TBSCertListGenerator(); + extGenerator = new ExtensionsGenerator(); + + tbsGen.setIssuer(issuer); + tbsGen.setThisUpdate(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) + { + return this.setNextUpdate(new Time(date)); + } + + /** + * Set the date by which the next CRL will become available. + * + * @param date date of next CRL update. + * @param dateLocale locale to be used for date interpretation. + * @return the current builder. + */ + public X509v2CRLBuilder setNextUpdate( + Date date, + Locale dateLocale) + { + return this.setNextUpdate(new Time(date, dateLocale)); + } + + /** + * 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( + Time date) + { + tbsGen.setNextUpdate(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; + } + + /** + * Add a given extension field for the standard extensions tag (tag 3) using a byte encoding of the + * extension value. + * + * @param oid the OID defining the extension type. + * @param isCritical true if the extension is critical, false otherwise. + * @param encodedValue a byte array representing the encoding of the extension value. + * @return this builder object. + */ + public X509v2CRLBuilder addExtension( + ASN1ObjectIdentifier oid, + boolean isCritical, + byte[] encodedValue) + throws CertIOException + { + extGenerator.addExtension(oid, isCritical, encodedValue); + + 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/pkix/src/main/java/org/spongycastle/cert/X509v3CertificateBuilder.java b/pkix/src/main/java/org/spongycastle/cert/X509v3CertificateBuilder.java new file mode 100644 index 00000000..0b55b2aa --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/cert/X509v3CertificateBuilder.java @@ -0,0 +1,195 @@ +package org.spongycastle.cert; + +import java.math.BigInteger; +import java.util.Date; +import java.util.Locale; + +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) + { + this(issuer, serial, new Time(notBefore), new Time(notAfter), subject, publicKeyInfo); + } + + /** + * Create a builder for a version 3 certificate. You may need to use this constructor if the default locale + * doesn't use a Gregorian calender so that the Time produced is compatible with other ASN.1 implementations. + * + * @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 dateLocale locale to be used for date interpretation. + * @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, Locale dateLocale, X500Name subject, SubjectPublicKeyInfo publicKeyInfo) + { + this(issuer, serial, new Time(notBefore, dateLocale), new Time(notAfter, dateLocale), subject, publicKeyInfo); + } + + /** + * Create a builder for a version 3 certificate. + * + * @param issuer the certificate issuer + * @param serial the certificate serial number + * @param notBefore the Time before which the certificate is not valid + * @param notAfter the Time 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, Time notBefore, Time notAfter, X500Name subject, SubjectPublicKeyInfo publicKeyInfo) + { + tbsGen = new V3TBSCertificateGenerator(); + tbsGen.setSerialNumber(new ASN1Integer(serial)); + tbsGen.setIssuer(issuer); + tbsGen.setStartDate(notBefore); + tbsGen.setEndDate(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) using a byte encoding of the + * extension value. + * + * @param oid the OID defining the extension type. + * @param isCritical true if the extension is critical, false otherwise. + * @param encodedValue a byte array representing the encoding of the extension value. + * @return this builder object. + */ + public X509v3CertificateBuilder addExtension( + ASN1ObjectIdentifier oid, + boolean isCritical, + byte[] encodedValue) + throws CertIOException + { + extGenerator.addExtension(oid, isCritical, encodedValue); + + 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/pkix/src/main/java/org/spongycastle/cert/bc/BcX509ExtensionUtils.java b/pkix/src/main/java/org/spongycastle/cert/bc/BcX509ExtensionUtils.java new file mode 100644 index 00000000..6d4dc217 --- /dev/null +++ b/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: + *
+     * (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).
+     * 
+ * @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/pkix/src/main/java/org/spongycastle/cert/bc/BcX509v1CertificateBuilder.java b/pkix/src/main/java/org/spongycastle/cert/bc/BcX509v1CertificateBuilder.java new file mode 100644 index 00000000..2036d5d5 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/bc/BcX509v3CertificateBuilder.java b/pkix/src/main/java/org/spongycastle/cert/bc/BcX509v3CertificateBuilder.java new file mode 100644 index 00000000..28264101 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/cmp/CMPException.java b/pkix/src/main/java/org/spongycastle/cert/cmp/CMPException.java new file mode 100644 index 00000000..dc02a03b --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/cmp/CMPRuntimeException.java b/pkix/src/main/java/org/spongycastle/cert/cmp/CMPRuntimeException.java new file mode 100644 index 00000000..07f5b819 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/cmp/CMPUtil.java b/pkix/src/main/java/org/spongycastle/cert/cmp/CMPUtil.java new file mode 100644 index 00000000..6198ca84 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/cmp/CertificateConfirmationContent.java b/pkix/src/main/java/org/spongycastle/cert/cmp/CertificateConfirmationContent.java new file mode 100644 index 00000000..0f860341 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/cmp/CertificateConfirmationContentBuilder.java b/pkix/src/main/java/org/spongycastle/cert/cmp/CertificateConfirmationContentBuilder.java new file mode 100644 index 00000000..106b3e24 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/cmp/CertificateStatus.java b/pkix/src/main/java/org/spongycastle/cert/cmp/CertificateStatus.java new file mode 100644 index 00000000..f2923878 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/cmp/GeneralPKIMessage.java b/pkix/src/main/java/org/spongycastle/cert/cmp/GeneralPKIMessage.java new file mode 100644 index 00000000..5bd49907 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/cmp/ProtectedPKIMessage.java b/pkix/src/main/java/org/spongycastle/cert/cmp/ProtectedPKIMessage.java new file mode 100644 index 00000000..b5dbb980 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/cmp/ProtectedPKIMessageBuilder.java b/pkix/src/main/java/org/spongycastle/cert/cmp/ProtectedPKIMessageBuilder.java new file mode 100644 index 00000000..8d5228d1 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/cmp/RevocationDetails.java b/pkix/src/main/java/org/spongycastle/cert/cmp/RevocationDetails.java new file mode 100644 index 00000000..a9c4993c --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/cmp/RevocationDetailsBuilder.java b/pkix/src/main/java/org/spongycastle/cert/cmp/RevocationDetailsBuilder.java new file mode 100644 index 00000000..bc7eaa04 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/crmf/AuthenticatorControl.java b/pkix/src/main/java/org/spongycastle/cert/crmf/AuthenticatorControl.java new file mode 100644 index 00000000..58e6c63a --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/crmf/CRMFException.java b/pkix/src/main/java/org/spongycastle/cert/crmf/CRMFException.java new file mode 100644 index 00000000..14aa0ad2 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/crmf/CRMFRuntimeException.java b/pkix/src/main/java/org/spongycastle/cert/crmf/CRMFRuntimeException.java new file mode 100644 index 00000000..cde484c4 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/crmf/CRMFUtil.java b/pkix/src/main/java/org/spongycastle/cert/crmf/CRMFUtil.java new file mode 100644 index 00000000..eb6771e6 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/crmf/CertificateRequestMessage.java b/pkix/src/main/java/org/spongycastle/cert/crmf/CertificateRequestMessage.java new file mode 100644 index 00000000..987b6b37 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/crmf/CertificateRequestMessageBuilder.java b/pkix/src/main/java/org/spongycastle/cert/crmf/CertificateRequestMessageBuilder.java new file mode 100644 index 00000000..4bfd6451 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/crmf/Control.java b/pkix/src/main/java/org/spongycastle/cert/crmf/Control.java new file mode 100644 index 00000000..4454f7e7 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/crmf/EncryptedValueBuilder.java b/pkix/src/main/java/org/spongycastle/cert/crmf/EncryptedValueBuilder.java new file mode 100644 index 00000000..6a34f49c --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/crmf/EncryptedValuePadder.java b/pkix/src/main/java/org/spongycastle/cert/crmf/EncryptedValuePadder.java new file mode 100644 index 00000000..d00b5b62 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/crmf/EncryptedValueParser.java b/pkix/src/main/java/org/spongycastle/cert/crmf/EncryptedValueParser.java new file mode 100644 index 00000000..80804993 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/crmf/PKIArchiveControl.java b/pkix/src/main/java/org/spongycastle/cert/crmf/PKIArchiveControl.java new file mode 100644 index 00000000..0c9bd985 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/crmf/PKIArchiveControlBuilder.java b/pkix/src/main/java/org/spongycastle/cert/crmf/PKIArchiveControlBuilder.java new file mode 100644 index 00000000..1ca860cb --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/crmf/PKMACBuilder.java b/pkix/src/main/java/org/spongycastle/cert/crmf/PKMACBuilder.java new file mode 100644 index 00000000..54e9cd91 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/crmf/PKMACValueGenerator.java b/pkix/src/main/java/org/spongycastle/cert/crmf/PKMACValueGenerator.java new file mode 100644 index 00000000..eaf215ff --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/crmf/PKMACValueVerifier.java b/pkix/src/main/java/org/spongycastle/cert/crmf/PKMACValueVerifier.java new file mode 100644 index 00000000..a65ff61d --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/crmf/PKMACValuesCalculator.java b/pkix/src/main/java/org/spongycastle/cert/crmf/PKMACValuesCalculator.java new file mode 100644 index 00000000..0b4f1407 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/crmf/ProofOfPossessionSigningKeyBuilder.java b/pkix/src/main/java/org/spongycastle/cert/crmf/ProofOfPossessionSigningKeyBuilder.java new file mode 100644 index 00000000..07659593 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/crmf/RegTokenControl.java b/pkix/src/main/java/org/spongycastle/cert/crmf/RegTokenControl.java new file mode 100644 index 00000000..b1a6eb7e --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/crmf/ValueDecryptorGenerator.java b/pkix/src/main/java/org/spongycastle/cert/crmf/ValueDecryptorGenerator.java new file mode 100644 index 00000000..3fcee4eb --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/crmf/bc/BcFixedLengthMGF1Padder.java b/pkix/src/main/java/org/spongycastle/cert/crmf/bc/BcFixedLengthMGF1Padder.java new file mode 100644 index 00000000..756acd7d --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/cert/crmf/bc/BcFixedLengthMGF1Padder.java @@ -0,0 +1,121 @@ +package org.spongycastle.cert.crmf.bc; + +import java.security.SecureRandom; + +import org.spongycastle.cert.crmf.EncryptedValuePadder; +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 BcFixedLengthMGF1Padder + 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 BcFixedLengthMGF1Padder(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 BcFixedLengthMGF1Padder(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/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/CRMFHelper.java b/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/CRMFHelper.java new file mode 100644 index 00000000..92d7faa6 --- /dev/null +++ b/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.util.JcaJceHelper; +import org.spongycastle.jcajce.util.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/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JcaCertificateRequestMessage.java b/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JcaCertificateRequestMessage.java new file mode 100644 index 00000000..9b10e78e --- /dev/null +++ b/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.util.DefaultJcaJceHelper; +import org.spongycastle.jcajce.util.NamedJcaJceHelper; +import org.spongycastle.jcajce.util.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/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JcaCertificateRequestMessageBuilder.java b/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JcaCertificateRequestMessageBuilder.java new file mode 100644 index 00000000..1d9318dd --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JcaEncryptedValueBuilder.java b/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JcaEncryptedValueBuilder.java new file mode 100644 index 00000000..bed393dc --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JcaPKIArchiveControlBuilder.java b/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JcaPKIArchiveControlBuilder.java new file mode 100644 index 00000000..de527e07 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JceAsymmetricValueDecryptorGenerator.java b/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JceAsymmetricValueDecryptorGenerator.java new file mode 100644 index 00000000..8ad510bb --- /dev/null +++ b/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.util.DefaultJcaJceHelper; +import org.spongycastle.jcajce.util.NamedJcaJceHelper; +import org.spongycastle.jcajce.util.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/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JceCRMFEncryptorBuilder.java b/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JceCRMFEncryptorBuilder.java new file mode 100644 index 00000000..6147184f --- /dev/null +++ b/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.util.DefaultJcaJceHelper; +import org.spongycastle.jcajce.util.NamedJcaJceHelper; +import org.spongycastle.jcajce.util.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/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JcePKMACValuesCalculator.java b/pkix/src/main/java/org/spongycastle/cert/crmf/jcajce/JcePKMACValuesCalculator.java new file mode 100644 index 00000000..ceaf78cb --- /dev/null +++ b/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.util.DefaultJcaJceHelper; +import org.spongycastle.jcajce.util.NamedJcaJceHelper; +import org.spongycastle.jcajce.util.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/pkix/src/main/java/org/spongycastle/cert/jcajce/CertHelper.java b/pkix/src/main/java/org/spongycastle/cert/jcajce/CertHelper.java new file mode 100644 index 00000000..1c5679bf --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/jcajce/DefaultCertHelper.java b/pkix/src/main/java/org/spongycastle/cert/jcajce/DefaultCertHelper.java new file mode 100644 index 00000000..d8713bf3 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaAttrCertStore.java b/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaAttrCertStore.java new file mode 100644 index 00000000..ed354335 --- /dev/null +++ b/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. + *

+ * The class will convert X509AttributeCertificate objects into X509AttributeCertificateHolder objects. + *

+ */ +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/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaAttributeCertificateIssuer.java b/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaAttributeCertificateIssuer.java new file mode 100644 index 00000000..54ee46ee --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaAttributeCertificateIssuer.java @@ -0,0 +1,32 @@ +package org.spongycastle.cert.jcajce; + +import java.security.cert.X509Certificate; + +import javax.security.auth.x500.X500Principal; + +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.cert.AttributeCertificateIssuer; + +public class JcaAttributeCertificateIssuer + extends AttributeCertificateIssuer +{ + /** + * Base constructor. + * + * @param issuerCert certificate for the issuer of the attribute certificate. + */ + public JcaAttributeCertificateIssuer(X509Certificate issuerCert) + { + this(issuerCert.getIssuerX500Principal()); + } + + /** + * Base constructor. + * + * @param issuerDN X.500 DN for the issuer of the attribute certificate. + */ + public JcaAttributeCertificateIssuer(X500Principal issuerDN) + { + super(X500Name.getInstance(issuerDN.getEncoded())); + } +} diff --git a/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaCRLStore.java b/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaCRLStore.java new file mode 100644 index 00000000..08493d13 --- /dev/null +++ b/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. + *

+ * The class will convert X509CRL objects into X509CRLHolder objects. + *

+ */ +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/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaCertStore.java b/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaCertStore.java new file mode 100644 index 00000000..49766814 --- /dev/null +++ b/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. + *

+ * The class will convert X509Certificate objects into X509CertificateHolder objects. + *

+ */ +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/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaCertStoreBuilder.java b/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaCertStoreBuilder.java new file mode 100644 index 00000000..fbb26cb4 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX500NameUtil.java b/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX500NameUtil.java new file mode 100644 index 00000000..d6b6ae89 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509AttributeCertificateHolder.java b/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509AttributeCertificateHolder.java new file mode 100644 index 00000000..35076252 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509CRLConverter.java b/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509CRLConverter.java new file mode 100644 index 00000000..7040f1aa --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509CRLHolder.java b/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509CRLHolder.java new file mode 100644 index 00000000..91bcd88d --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509CertificateConverter.java b/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509CertificateConverter.java new file mode 100644 index 00000000..5e46a17d --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509CertificateHolder.java b/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509CertificateHolder.java new file mode 100644 index 00000000..a523f975 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509ContentVerifierProviderBuilder.java b/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509ContentVerifierProviderBuilder.java new file mode 100644 index 00000000..46eb3b43 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509ExtensionUtils.java b/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509ExtensionUtils.java new file mode 100644 index 00000000..b601e24e --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509ExtensionUtils.java @@ -0,0 +1,145 @@ +package org.spongycastle.cert.jcajce; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; + +import javax.security.auth.x500.X500Principal; + +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.oiw.OIWObjectIdentifiers; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.AuthorityKeyIdentifier; +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.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())); + } + + public AuthorityKeyIdentifier createAuthorityKeyIdentifier(PublicKey pubKey, X500Principal name, BigInteger serial) + { + return super.createAuthorityKeyIdentifier(SubjectPublicKeyInfo.getInstance(pubKey.getEncoded()), new GeneralNames(new GeneralName(X500Name.getInstance(name.getEncoded()))), serial); + } + + public AuthorityKeyIdentifier createAuthorityKeyIdentifier(PublicKey pubKey, GeneralNames generalNames, BigInteger serial) + { + return super.createAuthorityKeyIdentifier(SubjectPublicKeyInfo.getInstance(pubKey.getEncoded()), generalNames, serial); + } + + /** + * Return a RFC 3280 type 1 key identifier. As in: + *
+     * (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).
+     * 
+ * @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: + *
+     * (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.
+     * 
+ * @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/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509v1CertificateBuilder.java b/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509v1CertificateBuilder.java new file mode 100644 index 00000000..e5e4f83d --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509v2CRLBuilder.java b/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509v2CRLBuilder.java new file mode 100644 index 00000000..97d544bd --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509v3CertificateBuilder.java b/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509v3CertificateBuilder.java new file mode 100644 index 00000000..238edbe8 --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/cert/jcajce/JcaX509v3CertificateBuilder.java @@ -0,0 +1,119 @@ +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.asn1.x509.Time; +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 a PublicKey. + * + * @param issuer X500Name representing the issuer of this certificate. + * @param serial the serial number for the certificate. + * @param notBefore Time before which the certificate is not valid. + * @param notAfter Time 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, Time notBefore, Time 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/pkix/src/main/java/org/spongycastle/cert/jcajce/NamedCertHelper.java b/pkix/src/main/java/org/spongycastle/cert/jcajce/NamedCertHelper.java new file mode 100644 index 00000000..89584138 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/jcajce/ProviderCertHelper.java b/pkix/src/main/java/org/spongycastle/cert/jcajce/ProviderCertHelper.java new file mode 100644 index 00000000..ffe37e9b --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/ocsp/BasicOCSPResp.java b/pkix/src/main/java/org/spongycastle/cert/ocsp/BasicOCSPResp.java new file mode 100644 index 00000000..f3c65670 --- /dev/null +++ b/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; + +/** + *
+ * BasicOCSPResponse       ::= SEQUENCE {
+ *    tbsResponseData      ResponseData,
+ *    signatureAlgorithm   AlgorithmIdentifier,
+ *    signature            BIT STRING,
+ *    certs                [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
+ * 
+ */ +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/pkix/src/main/java/org/spongycastle/cert/ocsp/BasicOCSPRespBuilder.java b/pkix/src/main/java/org/spongycastle/cert/ocsp/BasicOCSPRespBuilder.java new file mode 100644 index 00000000..ac759de6 --- /dev/null +++ b/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; + ASN1GeneralizedTime thisUpdate; + ASN1GeneralizedTime 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/pkix/src/main/java/org/spongycastle/cert/ocsp/CertificateID.java b/pkix/src/main/java/org/spongycastle/cert/ocsp/CertificateID.java new file mode 100644 index 00000000..aac029ca --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/ocsp/CertificateStatus.java b/pkix/src/main/java/org/spongycastle/cert/ocsp/CertificateStatus.java new file mode 100644 index 00000000..ba84b8f8 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPException.java b/pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPException.java new file mode 100644 index 00000000..be91e3d8 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPReq.java b/pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPReq.java new file mode 100644 index 00000000..17089673 --- /dev/null +++ b/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; + +/** + *
+ * 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 }
+ * 
+ */ +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/pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPReqBuilder.java b/pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPReqBuilder.java new file mode 100644 index 00000000..2bc0a6d1 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPResp.java b/pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPResp.java new file mode 100644 index 00000000..0b587f70 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPRespBuilder.java b/pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPRespBuilder.java new file mode 100644 index 00000000..fe2da11d --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPUtils.java b/pkix/src/main/java/org/spongycastle/cert/ocsp/OCSPUtils.java new file mode 100644 index 00000000..a58ca071 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/ocsp/Req.java b/pkix/src/main/java/org/spongycastle/cert/ocsp/Req.java new file mode 100644 index 00000000..52c174dd --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/ocsp/RespData.java b/pkix/src/main/java/org/spongycastle/cert/ocsp/RespData.java new file mode 100644 index 00000000..3db55a3e --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/ocsp/RespID.java b/pkix/src/main/java/org/spongycastle/cert/ocsp/RespID.java new file mode 100644 index 00000000..7510200d --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/ocsp/RevokedStatus.java b/pkix/src/main/java/org/spongycastle/cert/ocsp/RevokedStatus.java new file mode 100644 index 00000000..0842ea58 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/ocsp/SingleResp.java b/pkix/src/main/java/org/spongycastle/cert/ocsp/SingleResp.java new file mode 100644 index 00000000..98beb8b3 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/ocsp/UnknownStatus.java b/pkix/src/main/java/org/spongycastle/cert/ocsp/UnknownStatus.java new file mode 100644 index 00000000..42eda721 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/ocsp/jcajce/JcaBasicOCSPRespBuilder.java b/pkix/src/main/java/org/spongycastle/cert/ocsp/jcajce/JcaBasicOCSPRespBuilder.java new file mode 100644 index 00000000..cbd591db --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/ocsp/jcajce/JcaCertificateID.java b/pkix/src/main/java/org/spongycastle/cert/ocsp/jcajce/JcaCertificateID.java new file mode 100644 index 00000000..d59e7e04 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/ocsp/jcajce/JcaRespID.java b/pkix/src/main/java/org/spongycastle/cert/ocsp/jcajce/JcaRespID.java new file mode 100644 index 00000000..55a390be --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/path/CertPath.java b/pkix/src/main/java/org/spongycastle/cert/path/CertPath.java new file mode 100644 index 00000000..bbb20a4d --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/path/CertPathUtils.java b/pkix/src/main/java/org/spongycastle/cert/path/CertPathUtils.java new file mode 100644 index 00000000..257e0bb8 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/path/CertPathValidation.java b/pkix/src/main/java/org/spongycastle/cert/path/CertPathValidation.java new file mode 100644 index 00000000..11f48367 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/path/CertPathValidationContext.java b/pkix/src/main/java/org/spongycastle/cert/path/CertPathValidationContext.java new file mode 100644 index 00000000..010b4ef6 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/path/CertPathValidationException.java b/pkix/src/main/java/org/spongycastle/cert/path/CertPathValidationException.java new file mode 100644 index 00000000..0a1188d1 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/path/CertPathValidationResult.java b/pkix/src/main/java/org/spongycastle/cert/path/CertPathValidationResult.java new file mode 100644 index 00000000..276ef8d4 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/path/CertPathValidationResultBuilder.java b/pkix/src/main/java/org/spongycastle/cert/path/CertPathValidationResultBuilder.java new file mode 100644 index 00000000..80bf7ff2 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/path/validations/BasicConstraintsValidation.java b/pkix/src/main/java/org/spongycastle/cert/path/validations/BasicConstraintsValidation.java new file mode 100644 index 00000000..bfe0f1a4 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/path/validations/CRLValidation.java b/pkix/src/main/java/org/spongycastle/cert/path/validations/CRLValidation.java new file mode 100644 index 00000000..325126e1 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/path/validations/CertificatePoliciesValidation.java b/pkix/src/main/java/org/spongycastle/cert/path/validations/CertificatePoliciesValidation.java new file mode 100644 index 00000000..7015adb0 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/path/validations/CertificatePoliciesValidationBuilder.java b/pkix/src/main/java/org/spongycastle/cert/path/validations/CertificatePoliciesValidationBuilder.java new file mode 100644 index 00000000..44817b61 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/path/validations/KeyUsageValidation.java b/pkix/src/main/java/org/spongycastle/cert/path/validations/KeyUsageValidation.java new file mode 100644 index 00000000..7211b7cd --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/path/validations/ParentCertIssuedValidation.java b/pkix/src/main/java/org/spongycastle/cert/path/validations/ParentCertIssuedValidation.java new file mode 100644 index 00000000..dff47fb7 --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/path/validations/ValidationUtils.java b/pkix/src/main/java/org/spongycastle/cert/path/validations/ValidationUtils.java new file mode 100644 index 00000000..5dbf495a --- /dev/null +++ b/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/pkix/src/main/java/org/spongycastle/cert/selector/MSOutlookKeyIdCalculator.java b/pkix/src/main/java/org/spongycastle/cert/selector/MSOutlookKeyIdCalculator.java new file mode 100644 index 00000000..23748ac3 --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/cert/selector/MSOutlookKeyIdCalculator.java @@ -0,0 +1,422 @@ +package org.spongycastle.cert.selector; + +import java.io.IOException; + +import org.spongycastle.asn1.ASN1Encoding; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.util.Pack; + +class MSOutlookKeyIdCalculator +{ + // This is less than ideal, but it seems to be the best way of supporting this without exposing SHA-1 + // as the class is only used to workout the MSOutlook Key ID, you can think of the fact it's SHA-1 as + // a coincidence... + static byte[] calculateKeyId(SubjectPublicKeyInfo info) + { + SHA1Digest dig = new SHA1Digest(); + 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; + } + + private static abstract class GeneralDigest + { + private static final int BYTE_LENGTH = 64; + private byte[] xBuf; + private int xBufOff; + + private long byteCount; + + /** + * Standard constructor + */ + protected GeneralDigest() + { + xBuf = new byte[4]; + xBufOff = 0; + } + + /** + * Copy constructor. We are using copy constructors in place + * of the Object.clone() interface as this interface is not + * supported by J2ME. + */ + protected GeneralDigest(GeneralDigest t) + { + xBuf = new byte[t.xBuf.length]; + + copyIn(t); + } + + protected void copyIn(GeneralDigest t) + { + System.arraycopy(t.xBuf, 0, xBuf, 0, t.xBuf.length); + + xBufOff = t.xBufOff; + byteCount = t.byteCount; + } + + public void update( + byte in) + { + xBuf[xBufOff++] = in; + + if (xBufOff == xBuf.length) + { + processWord(xBuf, 0); + xBufOff = 0; + } + + byteCount++; + } + + public void update( + byte[] in, + int inOff, + int len) + { + // + // fill the current word + // + while ((xBufOff != 0) && (len > 0)) + { + update(in[inOff]); + + inOff++; + len--; + } + + // + // process whole words. + // + while (len > xBuf.length) + { + processWord(in, inOff); + + inOff += xBuf.length; + len -= xBuf.length; + byteCount += xBuf.length; + } + + // + // load in the remainder. + // + while (len > 0) + { + update(in[inOff]); + + inOff++; + len--; + } + } + + public void finish() + { + long bitLength = (byteCount << 3); + + // + // add the pad bytes. + // + update((byte)128); + + while (xBufOff != 0) + { + update((byte)0); + } + + processLength(bitLength); + + processBlock(); + } + + public void reset() + { + byteCount = 0; + + xBufOff = 0; + for (int i = 0; i < xBuf.length; i++) + { + xBuf[i] = 0; + } + } + + protected abstract void processWord(byte[] in, int inOff); + + protected abstract void processLength(long bitLength); + + protected abstract void processBlock(); + } + + private static class SHA1Digest + extends GeneralDigest + { + private static final int DIGEST_LENGTH = 20; + + private int H1, H2, H3, H4, H5; + + private int[] X = new int[80]; + private int xOff; + + /** + * Standard constructor + */ + public SHA1Digest() + { + reset(); + } + + public String getAlgorithmName() + { + return "SHA-1"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + protected void processWord( + byte[] in, + int inOff) + { + // Note: Inlined for performance + // X[xOff] = Pack.bigEndianToInt(in, inOff); + int n = in[ inOff] << 24; + n |= (in[++inOff] & 0xff) << 16; + n |= (in[++inOff] & 0xff) << 8; + n |= (in[++inOff] & 0xff); + X[xOff] = n; + + if (++xOff == 16) + { + processBlock(); + } + } + + protected void processLength( + long bitLength) + { + if (xOff > 14) + { + processBlock(); + } + + X[14] = (int)(bitLength >>> 32); + X[15] = (int)(bitLength & 0xffffffff); + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + Pack.intToBigEndian(H1, out, outOff); + Pack.intToBigEndian(H2, out, outOff + 4); + Pack.intToBigEndian(H3, out, outOff + 8); + Pack.intToBigEndian(H4, out, outOff + 12); + Pack.intToBigEndian(H5, out, outOff + 16); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables + */ + public void reset() + { + super.reset(); + + H1 = 0x67452301; + H2 = 0xefcdab89; + H3 = 0x98badcfe; + H4 = 0x10325476; + H5 = 0xc3d2e1f0; + + xOff = 0; + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } + + // + // Additive constants + // + private static final int Y1 = 0x5a827999; + private static final int Y2 = 0x6ed9eba1; + private static final int Y3 = 0x8f1bbcdc; + private static final int Y4 = 0xca62c1d6; + + private int f( + int u, + int v, + int w) + { + return ((u & v) | ((~u) & w)); + } + + private int h( + int u, + int v, + int w) + { + return (u ^ v ^ w); + } + + private int g( + int u, + int v, + int w) + { + return ((u & v) | (u & w) | (v & w)); + } + + protected void processBlock() + { + // + // expand 16 word block into 80 word block. + // + for (int i = 16; i < 80; i++) + { + int t = X[i - 3] ^ X[i - 8] ^ X[i - 14] ^ X[i - 16]; + X[i] = t << 1 | t >>> 31; + } + + // + // set up working variables. + // + int A = H1; + int B = H2; + int C = H3; + int D = H4; + int E = H5; + + // + // round 1 + // + int idx = 0; + + for (int j = 0; j < 4; j++) + { + // E = rotateLeft(A, 5) + f(B, C, D) + E + X[idx++] + Y1 + // B = rotateLeft(B, 30) + E += (A << 5 | A >>> 27) + f(B, C, D) + X[idx++] + Y1; + B = B << 30 | B >>> 2; + + D += (E << 5 | E >>> 27) + f(A, B, C) + X[idx++] + Y1; + A = A << 30 | A >>> 2; + + C += (D << 5 | D >>> 27) + f(E, A, B) + X[idx++] + Y1; + E = E << 30 | E >>> 2; + + B += (C << 5 | C >>> 27) + f(D, E, A) + X[idx++] + Y1; + D = D << 30 | D >>> 2; + + A += (B << 5 | B >>> 27) + f(C, D, E) + X[idx++] + Y1; + C = C << 30 | C >>> 2; + } + + // + // round 2 + // + for (int j = 0; j < 4; j++) + { + // E = rotateLeft(A, 5) + h(B, C, D) + E + X[idx++] + Y2 + // B = rotateLeft(B, 30) + E += (A << 5 | A >>> 27) + h(B, C, D) + X[idx++] + Y2; + B = B << 30 | B >>> 2; + + D += (E << 5 | E >>> 27) + h(A, B, C) + X[idx++] + Y2; + A = A << 30 | A >>> 2; + + C += (D << 5 | D >>> 27) + h(E, A, B) + X[idx++] + Y2; + E = E << 30 | E >>> 2; + + B += (C << 5 | C >>> 27) + h(D, E, A) + X[idx++] + Y2; + D = D << 30 | D >>> 2; + + A += (B << 5 | B >>> 27) + h(C, D, E) + X[idx++] + Y2; + C = C << 30 | C >>> 2; + } + + // + // round 3 + // + for (int j = 0; j < 4; j++) + { + // E = rotateLeft(A, 5) + g(B, C, D) + E + X[idx++] + Y3 + // B = rotateLeft(B, 30) + E += (A << 5 | A >>> 27) + g(B, C, D) + X[idx++] + Y3; + B = B << 30 | B >>> 2; + + D += (E << 5 | E >>> 27) + g(A, B, C) + X[idx++] + Y3; + A = A << 30 | A >>> 2; + + C += (D << 5 | D >>> 27) + g(E, A, B) + X[idx++] + Y3; + E = E << 30 | E >>> 2; + + B += (C << 5 | C >>> 27) + g(D, E, A) + X[idx++] + Y3; + D = D << 30 | D >>> 2; + + A += (B << 5 | B >>> 27) + g(C, D, E) + X[idx++] + Y3; + C = C << 30 | C >>> 2; + } + + // + // round 4 + // + for (int j = 0; j <= 3; j++) + { + // E = rotateLeft(A, 5) + h(B, C, D) + E + X[idx++] + Y4 + // B = rotateLeft(B, 30) + E += (A << 5 | A >>> 27) + h(B, C, D) + X[idx++] + Y4; + B = B << 30 | B >>> 2; + + D += (E << 5 | E >>> 27) + h(A, B, C) + X[idx++] + Y4; + A = A << 30 | A >>> 2; + + C += (D << 5 | D >>> 27) + h(E, A, B) + X[idx++] + Y4; + E = E << 30 | E >>> 2; + + B += (C << 5 | C >>> 27) + h(D, E, A) + X[idx++] + Y4; + D = D << 30 | D >>> 2; + + A += (B << 5 | B >>> 27) + h(C, D, E) + X[idx++] + Y4; + C = C << 30 | C >>> 2; + } + + + H1 += A; + H2 += B; + H3 += C; + H4 += D; + H5 += E; + + // + // reset start of the buffer. + // + xOff = 0; + for (int i = 0; i < 16; i++) + { + X[i] = 0; + } + } + } +} diff --git a/pkix/src/main/java/org/spongycastle/cert/selector/X509AttributeCertificateHolderSelector.java b/pkix/src/main/java/org/spongycastle/cert/selector/X509AttributeCertificateHolderSelector.java new file mode 100644 index 00000000..3ae30b33 --- /dev/null +++ b/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 Selector 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 true if the attribute certificate is a match + * false 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 + * 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. + *

+ * The returned collection is immutable. + * + * @return The collection of target groups. + */ + public Collection getTargetGroups() + { + return targetGroups; + } +} diff --git a/pkix/src/main/java/org/spongycastle/cert/selector/X509AttributeCertificateHolderSelectorBuilder.java b/pkix/src/main/java/org/spongycastle/cert/selector/X509AttributeCertificateHolderSelectorBuilder.java new file mode 100644 index 00000000..35df571d --- /dev/null +++ b/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 null 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 null + * 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 null 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 null + * 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 + * null 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 X509AttributeCertificateHolder + * must contain at least one of the specified target names. + *

+ * 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 null) + */ + public void addTargetName(GeneralName name) + { + targetNames.add(name); + } + + /** + * Adds a collection with target names criteria. If null is + * given any will do. + *

+ * 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 X509AttributeCertificateHolder + * must contain at least one of the specified target groups. + *

+ * 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 null) + */ + public void addTargetGroup(GeneralName group) + { + targetGroups.add(group); + } + + /** + * Adds a collection with target groups criteria. If null is + * given any will do. + *

+ * The collection consists of GeneralName objects or byte[]